aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDerek Sollenberger <djsollen@google.com>2012-12-19 13:22:28 -0500
committerDerek Sollenberger <djsollen@google.com>2013-01-08 08:57:34 -0500
commit1d0f5a1e3b467fc2211e2a94b3806770b228b6e0 (patch)
tree6bfd1cfa8ea8ec5e367c16ba3121485b8b2aec84
parent6699e7ea2e981dccc2f3c41b5dcf1c860b11558d (diff)
parent363e546ed626b6dbbc42f5db87b3594bc0b5944b (diff)
downloadskia-1d0f5a1e3b467fc2211e2a94b3806770b228b6e0.tar.gz
Merge Skia @6890
Change-Id: I12ae1c7ebdb566addf3d2783c8405793e842df8a
-rw-r--r--Android.mk24
-rw-r--r--bench/Android.mk7
-rw-r--r--bench/BitmapRectBench.cpp4
-rw-r--r--bench/BlurBench.cpp60
-rw-r--r--bench/DashBench.cpp67
-rw-r--r--bench/LineBench.cpp67
-rw-r--r--bench/Matrix44Bench.cpp172
-rw-r--r--bench/PathBench.cpp206
-rw-r--r--bench/RectBench.cpp142
-rw-r--r--bench/RepeatTileBench.cpp20
-rw-r--r--bench/SkBenchmark.h8
-rw-r--r--bench/bench_expectations.txt2066
-rw-r--r--bench/bench_util.py136
-rw-r--r--bench/benchmain.cpp13
-rwxr-xr-xbench/gen_skp_ranges.py159
-rw-r--r--experimental/AndroidPathRenderer/AndroidPathRenderer.cpp732
-rw-r--r--experimental/AndroidPathRenderer/AndroidPathRenderer.h94
-rw-r--r--experimental/AndroidPathRenderer/Vertex.h84
-rw-r--r--experimental/AndroidPathRenderer/cutils/compiler.h35
-rwxr-xr-xexperimental/Intersection/ActiveEdge_Test.cpp87
-rw-r--r--experimental/Intersection/CubicIntersection.cpp161
-rw-r--r--experimental/Intersection/CubicReduceOrder.cpp241
-rw-r--r--experimental/Intersection/CubicReduceOrder_Test.cpp144
-rw-r--r--experimental/Intersection/DataTypes.cpp84
-rw-r--r--experimental/Intersection/DataTypes.h213
-rw-r--r--experimental/Intersection/EdgeDemo.cpp343
-rw-r--r--experimental/Intersection/EdgeDemoApp.mm94
-rw-r--r--experimental/Intersection/EdgeWalker.cpp2705
-rw-r--r--experimental/Intersection/EdgeWalkerRectangles_Test.cpp470
-rw-r--r--experimental/Intersection/EdgeWalker_Test.h58
-rw-r--r--experimental/Intersection/EdgeWalker_TestUtility.cpp765
-rw-r--r--experimental/Intersection/Intersection_Tests.cpp66
-rw-r--r--experimental/Intersection/Intersection_Tests.h47
-rw-r--r--experimental/Intersection/Intersections.h200
-rw-r--r--experimental/Intersection/LineIntersection.cpp325
-rw-r--r--experimental/Intersection/LineQuadraticIntersection.cpp367
-rw-r--r--experimental/Intersection/LineQuadraticIntersection_Test.cpp235
-rw-r--r--experimental/Intersection/LineUtilities.cpp127
-rw-r--r--experimental/Intersection/MiniSimplify_Test.cpp99
-rw-r--r--experimental/Intersection/QuadraticImplicit.cpp230
-rw-r--r--experimental/Intersection/QuadraticIntersection_Test.cpp150
-rw-r--r--experimental/Intersection/QuadraticReduceOrder.cpp173
-rw-r--r--experimental/Intersection/QuarticRoot.cpp295
-rw-r--r--experimental/Intersection/ShapeOpRect4x4_Test.cpp103
-rw-r--r--experimental/Intersection/ShapeOps.cpp266
-rw-r--r--experimental/Intersection/ShapeOps.h40
-rw-r--r--experimental/Intersection/Simplify.cpp6268
-rw-r--r--experimental/Intersection/SimplifyFindNext_Test.cpp154
-rw-r--r--experimental/Intersection/SimplifyFindTop_Test.cpp222
-rw-r--r--experimental/Intersection/SimplifyNew_Test.cpp3616
-rw-r--r--experimental/Intersection/SimplifyRect4x4_Test.cpp201
-rw-r--r--experimental/Intersection/SkAntiEdge.cpp1082
-rw-r--r--experimental/Intersection/as.htm805
-rw-r--r--experimental/Intersection/bc.htm455
-rw-r--r--experimental/Intersection/op.htm3573
-rw-r--r--experimental/pixman/Pixman-version.h50
-rw-r--r--experimental/pixman/junk.cpp108
-rw-r--r--gm/Android.mk15
-rw-r--r--gm/aaclip.cpp21
-rw-r--r--gm/aarectmodes.cpp3
-rw-r--r--gm/arithmode.cpp2
-rw-r--r--gm/bitmaprect.cpp6
-rw-r--r--gm/blurrect.cpp160
-rw-r--r--gm/cmykjpeg.cpp6
-rw-r--r--gm/colorfilterimagefilter.cpp6
-rw-r--r--gm/colormatrix.cpp139
-rw-r--r--gm/dashing.cpp126
-rw-r--r--gm/drawbitmaprect.cpp2
-rw-r--r--gm/factory.cpp67
-rw-r--r--gm/fatpathfill.cpp100
-rw-r--r--gm/gm.cpp12
-rw-r--r--gm/gm.h12
-rw-r--r--gm/gmmain.cpp1642
-rw-r--r--gm/hairmodes.cpp12
-rw-r--r--gm/image.cpp48
-rw-r--r--gm/imagefiltersbase.cpp8
-rw-r--r--gm/imagefiltersgraph.cpp41
-rw-r--r--gm/lighting.cpp12
-rw-r--r--gm/modecolorfilters.cpp165
-rw-r--r--gm/ninepatchstretch.cpp2
-rw-r--r--gm/pathinterior.cpp114
-rw-r--r--gm/resources/plane.pngbin0 -> 5718 bytes
-rw-r--r--gm/rrect.cpp170
-rw-r--r--gm/rrects.cpp150
-rw-r--r--gm/shadertext.cpp2
-rw-r--r--gm/shadertext2.cpp2
-rw-r--r--gm/shadertext3.cpp4
-rw-r--r--gm/simpleaaclip.cpp16
-rw-r--r--gm/srcmode.cpp152
-rw-r--r--gm/strokerect.cpp113
-rw-r--r--gm/tablecolorfilter.cpp4
-rw-r--r--gm/testimagefilters.cpp8
-rw-r--r--gm/tests/inputs/different-pixels/8888/dashing2.pngbin0 -> 24333 bytes
-rw-r--r--gm/tests/inputs/empty-dir/README1
-rw-r--r--gm/tests/inputs/identical-bytes/8888/dashing2.pngbin0 -> 27267 bytes
-rw-r--r--gm/tests/inputs/identical-pixels/8888/dashing2.pngbin0 -> 25966 bytes
-rw-r--r--gm/tests/outputs/compared-against-different-pixels/output-expected/command_line1
-rw-r--r--gm/tests/outputs/compared-against-different-pixels/output-expected/images/8888/dashing2.pngbin0 -> 27267 bytes
-rw-r--r--gm/tests/outputs/compared-against-different-pixels/output-expected/json-summary.txt17
-rw-r--r--gm/tests/outputs/compared-against-different-pixels/output-expected/return_value1
-rw-r--r--gm/tests/outputs/compared-against-different-pixels/output-expected/stdout6
-rw-r--r--gm/tests/outputs/compared-against-empty-dir/output-expected/command_line1
-rw-r--r--gm/tests/outputs/compared-against-empty-dir/output-expected/images/8888/dashing2.pngbin0 -> 27267 bytes
-rw-r--r--gm/tests/outputs/compared-against-empty-dir/output-expected/json-summary.txt17
-rw-r--r--gm/tests/outputs/compared-against-empty-dir/output-expected/return_value1
-rw-r--r--gm/tests/outputs/compared-against-empty-dir/output-expected/stdout5
-rw-r--r--gm/tests/outputs/compared-against-identical-bytes/output-expected/command_line1
-rw-r--r--gm/tests/outputs/compared-against-identical-bytes/output-expected/images/8888/dashing2.pngbin0 -> 27267 bytes
-rw-r--r--gm/tests/outputs/compared-against-identical-bytes/output-expected/json-summary.txt17
-rw-r--r--gm/tests/outputs/compared-against-identical-bytes/output-expected/return_value1
-rw-r--r--gm/tests/outputs/compared-against-identical-bytes/output-expected/stdout4
-rw-r--r--gm/tests/outputs/compared-against-identical-pixels/output-expected/command_line1
-rw-r--r--gm/tests/outputs/compared-against-identical-pixels/output-expected/images/8888/dashing2.pngbin0 -> 27267 bytes
-rw-r--r--gm/tests/outputs/compared-against-identical-pixels/output-expected/json-summary.txt17
-rw-r--r--gm/tests/outputs/compared-against-identical-pixels/output-expected/return_value1
-rw-r--r--gm/tests/outputs/compared-against-identical-pixels/output-expected/stdout4
-rwxr-xr-xgm/tests/rebaseline.sh38
-rwxr-xr-xgm/tests/run.sh79
-rw-r--r--gm/texdata.cpp8
-rw-r--r--gm/tilemodes.cpp138
-rw-r--r--gm/tinybitmap.cpp5
-rw-r--r--gm/xfermodes.cpp4
-rw-r--r--gyp/SampleApp.gyp10
-rw-r--r--gyp/all.gyp41
-rw-r--r--gyp/android_system.gyp2
-rw-r--r--gyp/apptype_console.gypi5
-rw-r--r--gyp/bench.gypi2
-rw-r--r--gyp/common.gypi22
-rw-r--r--gyp/common_conditions.gypi42
-rw-r--r--gyp/common_variables.gypi10
-rw-r--r--gyp/core.gyp2
-rw-r--r--gyp/core.gypi13
-rw-r--r--gyp/debugger.gyp8
-rw-r--r--gyp/effects.gypi3
-rw-r--r--gyp/everything.gyp31
-rw-r--r--gyp/experimental.gyp2
-rw-r--r--gyp/freetype.gyp104
-rw-r--r--gyp/gm.gyp3
-rw-r--r--gyp/gmslides.gypi9
-rw-r--r--gyp/gpu.gyp17
-rw-r--r--gyp/gpu.gypi31
-rw-r--r--gyp/images.gyp6
-rw-r--r--gyp/jsoncpp.gyp56
-rw-r--r--gyp/most.gyp34
-rw-r--r--gyp/nacl.gyp19
-rw-r--r--gyp/opts.gyp7
-rw-r--r--gyp/ports.gyp72
-rw-r--r--gyp/sfnt.gyp5
-rw-r--r--gyp/shapeops_edge.gyp1
-rw-r--r--gyp/tests.gyp10
-rw-r--r--gyp/tools.gyp52
-rw-r--r--gyp/utils.gyp52
-rw-r--r--gyp/views.gyp19
-rw-r--r--gyp/xml.gyp2
-rw-r--r--gyp/zlib.gyp2
-rwxr-xr-xgyp_skia127
-rw-r--r--include/core/SkBitmap.h20
-rw-r--r--include/core/SkCanvas.h94
-rw-r--r--include/core/SkChecksum.h6
-rw-r--r--include/core/SkClipStack.h304
-rw-r--r--include/core/SkColor.h26
-rw-r--r--include/core/SkColorFilter.h21
-rw-r--r--include/core/SkComposeShader.h9
-rw-r--r--include/core/SkData.h2
-rw-r--r--include/core/SkDraw.h2
-rw-r--r--include/core/SkDrawFilter.h11
-rw-r--r--include/core/SkFloatingPoint.h6
-rw-r--r--include/core/SkImage.h11
-rw-r--r--include/core/SkImageFilter.h22
-rw-r--r--include/core/SkMaskFilter.h40
-rw-r--r--include/core/SkMatrix.h15
-rw-r--r--include/core/SkOSFile.h21
-rw-r--r--include/core/SkPath.h249
-rw-r--r--include/core/SkPathEffect.h142
-rw-r--r--include/core/SkPicture.h33
-rw-r--r--include/core/SkPixelRef.h49
-rw-r--r--include/core/SkPostConfig.h8
-rw-r--r--include/core/SkPreConfig.h2
-rw-r--r--include/core/SkRRect.h270
-rw-r--r--include/core/SkRandom.h24
-rw-r--r--include/core/SkRasterizer.h4
-rw-r--r--include/core/SkReader32.h6
-rw-r--r--include/core/SkRect.h72
-rw-r--r--include/core/SkRefCnt.h10
-rw-r--r--include/core/SkScalar.h23
-rw-r--r--include/core/SkShader.h61
-rw-r--r--include/core/SkSize.h6
-rw-r--r--include/core/SkString.h39
-rw-r--r--include/core/SkStrokeRec.h92
-rw-r--r--include/core/SkSurface.h10
-rw-r--r--include/core/SkTDLinkedList.h179
-rw-r--r--include/core/SkTInternalLList.h272
-rw-r--r--include/core/SkTLazy.h11
-rw-r--r--include/core/SkTemplates.h1
-rw-r--r--include/core/SkTileGridPicture.h29
-rw-r--r--include/core/SkTypes.h21
-rw-r--r--include/core/SkUserConfig.h10
-rw-r--r--include/core/SkWriter32.h9
-rw-r--r--include/core/SkXfermode.h30
-rw-r--r--include/effects/Sk1DPathEffect.h14
-rw-r--r--include/effects/Sk2DPathEffect.h19
-rw-r--r--include/effects/SkAvoidXfermode.h8
-rw-r--r--include/effects/SkBlurDrawLooper.h2
-rwxr-xr-xinclude/effects/SkColorFilterImageFilter.h3
-rw-r--r--include/effects/SkColorMatrix.h6
-rw-r--r--include/effects/SkColorMatrixFilter.h16
-rw-r--r--include/effects/SkCornerPathEffect.h5
-rw-r--r--include/effects/SkDashPathEffect.h14
-rw-r--r--include/effects/SkDiscretePathEffect.h3
-rw-r--r--include/effects/SkEmbossMaskFilter.h4
-rw-r--r--include/effects/SkKernel33MaskFilter.h10
-rw-r--r--include/effects/SkLayerRasterizer.h2
-rw-r--r--include/effects/SkMagnifierImageFilter.h4
-rw-r--r--include/effects/SkMatrixConvolutionImageFilter.h2
-rwxr-xr-xinclude/effects/SkMergeImageFilter.h46
-rw-r--r--include/effects/SkOffsetImageFilter.h33
-rw-r--r--include/effects/SkPaintFlagsDrawFilter.h3
-rw-r--r--include/effects/SkStippleMaskFilter.h4
-rw-r--r--include/effects/SkTableMaskFilter.h8
-rwxr-xr-xinclude/effects/SkTestImageFilters.h58
-rw-r--r--include/gpu/GrAARectRenderer.h4
-rw-r--r--include/gpu/GrBackendEffectFactory.h85
-rw-r--r--include/gpu/GrConfig.h37
-rw-r--r--include/gpu/GrContext.h116
-rw-r--r--include/gpu/GrContextFactory.h5
-rw-r--r--include/gpu/GrCustomStageUnitTest.h78
-rw-r--r--include/gpu/GrEffect.h (renamed from include/gpu/GrCustomStage.h)56
-rw-r--r--include/gpu/GrEffectStage.h129
-rw-r--r--include/gpu/GrEffectUnitTest.h87
-rw-r--r--include/gpu/GrMatrix.h19
-rw-r--r--include/gpu/GrPaint.h103
-rw-r--r--include/gpu/GrPathRendererChain.h83
-rw-r--r--include/gpu/GrPoint.h2
-rw-r--r--include/gpu/GrProgramStageFactory.h131
-rw-r--r--include/gpu/GrRenderTarget.h39
-rw-r--r--include/gpu/GrResource.h6
-rw-r--r--include/gpu/GrSamplerState.h101
-rw-r--r--include/gpu/GrScalar.h49
-rw-r--r--include/gpu/GrSurface.h31
-rw-r--r--include/gpu/GrTBackendEffectFactory.h73
-rw-r--r--include/gpu/GrTexture.h12
-rw-r--r--include/gpu/GrTextureAccess.h6
-rw-r--r--include/gpu/GrTypes.h111
-rw-r--r--include/gpu/GrUserConfig.h9
-rw-r--r--include/gpu/SkGpuDevice.h8
-rw-r--r--include/gpu/SkGrPixelRef.h2
-rw-r--r--include/gpu/gl/GrGLFunctions.h4
-rw-r--r--include/gpu/gl/SkNativeGLContext.h19
-rw-r--r--include/images/SkBitmapFactory.h46
-rw-r--r--include/images/SkFlipPixelRef.h25
-rw-r--r--include/images/SkImageEncoder.h30
-rw-r--r--include/utils/SkCondVar.h68
-rw-r--r--include/utils/SkCountdown.h36
-rw-r--r--include/utils/SkDeferredCanvas.h13
-rw-r--r--include/utils/SkDumpCanvas.h7
-rw-r--r--include/utils/SkMatrix44.h258
-rw-r--r--include/utils/SkNWayCanvas.h5
-rw-r--r--include/utils/SkPictureUtils.h31
-rw-r--r--include/utils/SkProxyCanvas.h5
-rw-r--r--include/utils/SkRunnable.h17
-rw-r--r--include/utils/SkThreadPool.h50
-rw-r--r--include/views/SkOSWindow_NaCl.h46
-rw-r--r--include/views/SkWindow.h4
-rw-r--r--samplecode/ClockFaceView.cpp257
-rw-r--r--samplecode/GMSampleView.h78
-rw-r--r--samplecode/SampleApp.cpp2504
-rw-r--r--samplecode/SampleApp.h227
-rw-r--r--samplecode/SampleColorFilter.cpp219
-rw-r--r--samplecode/SampleDither.cpp185
-rw-r--r--samplecode/SampleFatBits.cpp459
-rw-r--r--samplecode/SampleHairline.cpp279
-rw-r--r--samplecode/SampleLayerMask.cpp75
-rw-r--r--samplecode/SampleLayers.cpp275
-rw-r--r--samplecode/SampleMipMap.cpp161
-rw-r--r--samplecode/SampleOvalTest.cpp117
-rw-r--r--samplecode/SamplePictFile.cpp142
-rw-r--r--samplecode/SampleRegion.cpp418
-rw-r--r--samplecode/SampleShaderText.cpp216
-rw-r--r--samplecode/SampleStrokeText.cpp149
-rw-r--r--samplecode/SampleText.cpp330
-rw-r--r--samplecode/SampleTiling.cpp174
-rw-r--r--skia.gyp28
-rw-r--r--src/animator/SkDisplayEvent.cpp2
-rw-r--r--src/animator/SkDisplayEvent.h2
-rw-r--r--src/animator/SkDisplayPost.cpp2
-rw-r--r--src/animator/SkDisplayPost.h2
-rw-r--r--src/animator/SkDisplayXMLParser.cpp2
-rw-r--r--src/animator/SkDisplayable.cpp2
-rw-r--r--src/animator/SkDisplayable.h2
-rw-r--r--src/animator/SkDrawExtraPathEffect.cpp14
-rw-r--r--src/animator/SkDrawGradient.cpp2
-rw-r--r--src/animator/SkDrawGradient.h2
-rw-r--r--src/animator/SkDrawGroup.cpp4
-rw-r--r--src/animator/SkDrawGroup.h2
-rw-r--r--src/animator/SkDrawMatrix.cpp2
-rw-r--r--src/animator/SkDrawMatrix.h2
-rw-r--r--src/animator/SkDrawPath.cpp4
-rw-r--r--src/animator/SkDrawPath.h4
-rw-r--r--src/core/SkAdvancedTypefaceMetrics.cpp2
-rw-r--r--src/core/SkBBoxHierarchyRecord.cpp6
-rw-r--r--src/core/SkBBoxHierarchyRecord.h3
-rw-r--r--src/core/SkBBoxRecord.cpp42
-rw-r--r--src/core/SkBBoxRecord.h2
-rw-r--r--src/core/SkBitmap.cpp214
-rw-r--r--src/core/SkBitmapHeap.cpp8
-rw-r--r--src/core/SkBitmapHeap.h1
-rw-r--r--src/core/SkBitmapProcShader.cpp24
-rw-r--r--src/core/SkBitmapProcShader.h3
-rw-r--r--src/core/SkBitmapProcState.cpp169
-rw-r--r--src/core/SkBitmapProcState.h3
-rw-r--r--src/core/SkBlitter.cpp73
-rw-r--r--src/core/SkBlitter.h8
-rw-r--r--src/core/SkBlitter_ARGB32.cpp169
-rw-r--r--src/core/SkCanvas.cpp217
-rw-r--r--src/core/SkClipStack.cpp970
-rw-r--r--src/core/SkColorFilter.cpp42
-rw-r--r--src/core/SkComposeShader.cpp41
-rw-r--r--src/core/SkCoreBlitters.h9
-rw-r--r--src/core/SkDevice.cpp2
-rw-r--r--src/core/SkDraw.cpp201
-rw-r--r--src/core/SkEdge.cpp6
-rw-r--r--src/core/SkEdge.h12
-rw-r--r--src/core/SkFilterShader.h14
-rw-r--r--src/core/SkGeometry.cpp66
-rw-r--r--src/core/SkGlyphCache.cpp4
-rw-r--r--src/core/SkImageFilter.cpp24
-rw-r--r--src/core/SkMaskFilter.cpp210
-rw-r--r--src/core/SkMaskGamma.h75
-rw-r--r--src/core/SkMatrix.cpp3
-rw-r--r--src/core/SkOrderedReadBuffer.cpp1
-rw-r--r--src/core/SkOrderedWriteBuffer.cpp103
-rw-r--r--src/core/SkOrderedWriteBuffer.h24
-rw-r--r--src/core/SkPaint.cpp23
-rw-r--r--src/core/SkPath.cpp362
-rw-r--r--src/core/SkPathEffect.cpp109
-rw-r--r--src/core/SkPicture.cpp41
-rw-r--r--src/core/SkPictureFlat.cpp1
-rw-r--r--src/core/SkPictureFlat.h46
-rw-r--r--src/core/SkPicturePlayback.cpp143
-rw-r--r--src/core/SkPicturePlayback.h17
-rw-r--r--src/core/SkPictureRecord.cpp120
-rw-r--r--src/core/SkPictureRecord.h17
-rw-r--r--src/core/SkPixelRef.cpp6
-rw-r--r--src/core/SkRRect.cpp328
-rw-r--r--src/core/SkRasterizer.cpp4
-rw-r--r--src/core/SkRegion.cpp24
-rw-r--r--src/core/SkScalerContext.cpp158
-rw-r--r--src/core/SkScalerContext.h14
-rw-r--r--src/core/SkScan_Antihair.cpp309
-rw-r--r--src/core/SkScan_Path.cpp6
-rw-r--r--src/core/SkShader.cpp82
-rw-r--r--src/core/SkString.cpp19
-rw-r--r--src/core/SkStroke.cpp97
-rw-r--r--src/core/SkStroke.h11
-rw-r--r--src/core/SkStrokeRec.cpp106
-rw-r--r--src/core/SkTLList.h385
-rw-r--r--src/core/SkTileGrid.cpp105
-rw-r--r--src/core/SkTileGrid.h120
-rw-r--r--src/core/SkTileGridPicture.cpp24
-rw-r--r--src/core/SkWriter32.cpp61
-rw-r--r--src/core/SkXfermode.cpp62
-rw-r--r--src/device/xps/SkXPSDevice.cpp2425
-rw-r--r--src/effects/Sk1DPathEffect.cpp9
-rw-r--r--src/effects/Sk2DPathEffect.cpp19
-rw-r--r--src/effects/SkArithmeticMode.cpp4
-rw-r--r--src/effects/SkAvoidXfermode.cpp39
-rw-r--r--src/effects/SkBlendImageFilter.cpp103
-rw-r--r--src/effects/SkBlurMask.cpp361
-rw-r--r--src/effects/SkBlurMask.h7
-rw-r--r--src/effects/SkBlurMaskFilter.cpp169
-rwxr-xr-xsrc/effects/SkColorFilterImageFilter.cpp46
-rw-r--r--src/effects/SkColorFilters.cpp23
-rw-r--r--src/effects/SkColorMatrixFilter.cpp203
-rw-r--r--src/effects/SkCornerPathEffect.cpp13
-rw-r--r--src/effects/SkDashPathEffect.cpp225
-rw-r--r--src/effects/SkDiscretePathEffect.cpp2
-rw-r--r--src/effects/SkEmbossMaskFilter.cpp4
-rw-r--r--src/effects/SkKernel33MaskFilter.cpp6
-rw-r--r--src/effects/SkLayerRasterizer.cpp2
-rw-r--r--src/effects/SkLightingImageFilter.cpp473
-rw-r--r--src/effects/SkMagnifierImageFilter.cpp161
-rw-r--r--src/effects/SkMatrixConvolutionImageFilter.cpp200
-rwxr-xr-xsrc/effects/SkMergeImageFilter.cpp165
-rw-r--r--src/effects/SkMorphologyImageFilter.cpp119
-rw-r--r--src/effects/SkOffsetImageFilter.cpp49
-rw-r--r--src/effects/SkPaintFlagsDrawFilter.cpp5
-rw-r--r--src/effects/SkSingleInputImageFilter.cpp2
-rw-r--r--src/effects/SkStippleMaskFilter.cpp2
-rw-r--r--src/effects/SkTableColorFilter.cpp179
-rw-r--r--src/effects/SkTableMaskFilter.cpp10
-rwxr-xr-xsrc/effects/SkTestImageFilters.cpp186
-rw-r--r--src/effects/gradients/SkGradientShader.cpp64
-rw-r--r--src/effects/gradients/SkGradientShaderPriv.h88
-rw-r--r--src/effects/gradients/SkLinearGradient.cpp102
-rw-r--r--src/effects/gradients/SkLinearGradient.h2
-rw-r--r--src/effects/gradients/SkRadialGradient.cpp101
-rw-r--r--src/effects/gradients/SkRadialGradient.h2
-rw-r--r--src/effects/gradients/SkSweepGradient.cpp97
-rw-r--r--src/effects/gradients/SkSweepGradient.h2
-rw-r--r--src/effects/gradients/SkTwoPointConicalGradient.cpp458
-rw-r--r--src/effects/gradients/SkTwoPointConicalGradient.h2
-rw-r--r--src/effects/gradients/SkTwoPointRadialGradient.cpp358
-rw-r--r--src/effects/gradients/SkTwoPointRadialGradient.h2
-rw-r--r--src/gpu/GrAAConvexPathRenderer.cpp65
-rw-r--r--src/gpu/GrAAConvexPathRenderer.h4
-rw-r--r--src/gpu/GrAAHairLinePathRenderer.cpp48
-rw-r--r--src/gpu/GrAAHairLinePathRenderer.h8
-rw-r--r--src/gpu/GrAARectRenderer.cpp98
-rw-r--r--src/gpu/GrAddPathRenderers_default.cpp14
-rw-r--r--src/gpu/GrClipMaskCache.h48
-rw-r--r--src/gpu/GrClipMaskManager.cpp1109
-rw-r--r--src/gpu/GrClipMaskManager.h67
-rw-r--r--src/gpu/GrContext.cpp329
-rw-r--r--src/gpu/GrCustomStage.cpp77
-rw-r--r--src/gpu/GrDefaultPathRenderer.cpp99
-rw-r--r--src/gpu/GrDefaultPathRenderer.h56
-rw-r--r--src/gpu/GrDrawState.cpp28
-rw-r--r--src/gpu/GrDrawState.h222
-rw-r--r--src/gpu/GrDrawTarget.cpp110
-rw-r--r--src/gpu/GrDrawTarget.h85
-rw-r--r--src/gpu/GrEffect.cpp102
-rw-r--r--src/gpu/GrGpu.cpp26
-rw-r--r--src/gpu/GrGpu.h52
-rw-r--r--src/gpu/GrGpuFactory.cpp6
-rw-r--r--src/gpu/GrGpuVertex.h4
-rw-r--r--src/gpu/GrInOrderDrawBuffer.cpp49
-rw-r--r--src/gpu/GrInOrderDrawBuffer.h12
-rw-r--r--src/gpu/GrMatrix.cpp713
-rw-r--r--src/gpu/GrPathRenderer.h165
-rw-r--r--src/gpu/GrPathRendererChain.cpp42
-rw-r--r--src/gpu/GrPathRendererChain.h68
-rw-r--r--src/gpu/GrPathUtils.cpp68
-rw-r--r--src/gpu/GrPathUtils.h18
-rw-r--r--src/gpu/GrRandom.h55
-rw-r--r--src/gpu/GrRectanizer.cpp2
-rw-r--r--src/gpu/GrRedBlackTree.h4
-rw-r--r--src/gpu/GrReducedClip.cpp415
-rw-r--r--src/gpu/GrReducedClip.h40
-rw-r--r--src/gpu/GrRenderTarget.cpp4
-rw-r--r--src/gpu/GrResourceCache.cpp68
-rw-r--r--src/gpu/GrResourceCache.h70
-rw-r--r--src/gpu/GrSWMaskHelper.cpp59
-rw-r--r--src/gpu/GrSWMaskHelper.h15
-rw-r--r--src/gpu/GrSoftwarePathRenderer.cpp27
-rw-r--r--src/gpu/GrSoftwarePathRenderer.h18
-rw-r--r--src/gpu/GrStencilAndCoverPathRenderer.cpp42
-rw-r--r--src/gpu/GrStencilAndCoverPathRenderer.h32
-rw-r--r--src/gpu/GrStencilBuffer.h48
-rw-r--r--src/gpu/GrTextContext.cpp28
-rw-r--r--src/gpu/SkGpuDevice.cpp306
-rw-r--r--src/gpu/SkGrPixelRef.cpp31
-rw-r--r--src/gpu/effects/Gr1DKernelEffect.h4
-rw-r--r--src/gpu/effects/GrColorTableEffect.cpp122
-rw-r--r--src/gpu/effects/GrColorTableEffect.h42
-rw-r--r--src/gpu/effects/GrConfigConversionEffect.cpp136
-rw-r--r--src/gpu/effects/GrConfigConversionEffect.h21
-rw-r--r--src/gpu/effects/GrConvolutionEffect.cpp114
-rw-r--r--src/gpu/effects/GrConvolutionEffect.h15
-rw-r--r--src/gpu/effects/GrSingleTextureEffect.cpp94
-rw-r--r--src/gpu/effects/GrSingleTextureEffect.h46
-rw-r--r--src/gpu/effects/GrTextureDomainEffect.cpp190
-rw-r--r--src/gpu/effects/GrTextureDomainEffect.h62
-rw-r--r--src/gpu/effects/GrTextureStripAtlas.h4
-rw-r--r--src/gpu/gl/GrGLCaps.cpp10
-rw-r--r--src/gpu/gl/GrGLCaps.h4
-rw-r--r--src/gpu/gl/GrGLContextInfo.cpp4
-rw-r--r--src/gpu/gl/GrGLContextInfo.h4
-rw-r--r--src/gpu/gl/GrGLCreateNullInterface.cpp4
-rw-r--r--src/gpu/gl/GrGLEffect.cpp33
-rw-r--r--src/gpu/gl/GrGLEffect.h95
-rw-r--r--src/gpu/gl/GrGLEffectMatrix.cpp213
-rw-r--r--src/gpu/gl/GrGLEffectMatrix.h97
-rw-r--r--src/gpu/gl/GrGLProgram.cpp302
-rw-r--r--src/gpu/gl/GrGLProgram.h123
-rw-r--r--src/gpu/gl/GrGLProgramStage.cpp40
-rw-r--r--src/gpu/gl/GrGLProgramStage.h90
-rw-r--r--src/gpu/gl/GrGLRenderTarget.cpp7
-rw-r--r--src/gpu/gl/GrGLRenderTarget.h10
-rw-r--r--src/gpu/gl/GrGLSL.cpp5
-rw-r--r--src/gpu/gl/GrGLSL.h18
-rw-r--r--src/gpu/gl/GrGLShaderBuilder.cpp105
-rw-r--r--src/gpu/gl/GrGLShaderBuilder.h77
-rw-r--r--src/gpu/gl/GrGLShaderVar.h35
-rw-r--r--src/gpu/gl/GrGLTexture.cpp9
-rw-r--r--src/gpu/gl/GrGLTexture.h20
-rw-r--r--src/gpu/gl/GrGLUniformManager.cpp20
-rw-r--r--src/gpu/gl/GrGLUniformManager.h4
-rw-r--r--src/gpu/gl/GrGLUtil.cpp17
-rw-r--r--src/gpu/gl/GrGLUtil.h10
-rw-r--r--src/gpu/gl/GrGpuGL.cpp74
-rw-r--r--src/gpu/gl/GrGpuGL.h41
-rw-r--r--src/gpu/gl/GrGpuGL_program.cpp245
-rw-r--r--src/gpu/gl/android/GrGLCreateNativeInterface_android.cpp4
-rw-r--r--src/gpu/gl/debug/GrGLCreateDebugInterface.cpp4
-rw-r--r--src/gpu/gl/mac/GrGLCreateNativeInterface_mac.cpp280
-rw-r--r--src/gpu/gl/nacl/SkNativeGLContext_nacl.cpp34
-rw-r--r--src/gpu/gr_unittests.cpp2
-rw-r--r--src/image/SkImagePriv.cpp3
-rw-r--r--src/image/SkImagePriv.h10
-rw-r--r--src/image/SkImage_Base.h2
-rw-r--r--src/image/SkImage_Gpu.cpp80
-rw-r--r--src/image/SkImage_Raster.cpp23
-rw-r--r--src/image/SkSurface.cpp6
-rw-r--r--src/image/SkSurface_Base.h4
-rw-r--r--src/image/SkSurface_Gpu.cpp161
-rw-r--r--src/image/SkSurface_Picture.cpp12
-rw-r--r--src/image/SkSurface_Raster.cpp38
-rw-r--r--src/images/SkBitmapFactory.cpp41
-rw-r--r--src/images/SkFlipPixelRef.cpp23
-rw-r--r--src/images/SkImageDecoder_libpng.cpp96
-rw-r--r--src/images/SkImages.cpp2
-rw-r--r--src/images/transform_scanline.h140
-rw-r--r--src/pdf/SkPDFDevice.cpp47
-rw-r--r--src/pdf/SkPDFFont.cpp30
-rw-r--r--src/pdf/SkPDFFormXObject.cpp9
-rw-r--r--src/pdf/SkPDFShader.cpp7
-rw-r--r--src/pipe/SkGPipePriv.h3
-rw-r--r--src/pipe/SkGPipeRead.cpp29
-rw-r--r--src/pipe/SkGPipeWrite.cpp40
-rw-r--r--src/ports/FontHostConfiguration_android.cpp22
-rw-r--r--src/ports/FontHostConfiguration_android.h23
-rw-r--r--src/ports/SkDebug_android.cpp13
-rw-r--r--src/ports/SkDebug_nacl.cpp39
-rw-r--r--src/ports/SkFontHost_FreeType.cpp50
-rw-r--r--src/ports/SkFontHost_FreeType_common.cpp31
-rw-r--r--src/ports/SkFontHost_FreeType_common.h2
-rw-r--r--src/ports/SkFontHost_android.cpp22
-rw-r--r--src/ports/SkFontHost_fontconfig.cpp255
-rw-r--r--src/ports/SkFontHost_mac_coretext.cpp883
-rwxr-xr-xsrc/ports/SkFontHost_win.cpp89
-rw-r--r--src/ports/SkFontHost_win_dw.cpp34
-rw-r--r--src/ports/SkGlobalInitialization_default.cpp2
-rw-r--r--src/ports/SkImageDecoder_CG.cpp24
-rw-r--r--src/ports/SkImageDecoder_WIC.cpp25
-rw-r--r--src/ports/SkImageRef_ashmem.cpp3
-rw-r--r--src/ports/SkOSFile_stdio.cpp54
-rw-r--r--src/ports/SkThread_win.cpp41
-rw-r--r--src/sfnt/SkOTTableTypes.h48
-rw-r--r--src/sfnt/SkOTTable_OS_2.h52
-rw-r--r--src/sfnt/SkOTTable_OS_2_V0.h149
-rw-r--r--src/sfnt/SkOTTable_OS_2_V1.h518
-rw-r--r--src/sfnt/SkOTTable_OS_2_V2.h540
-rw-r--r--src/sfnt/SkOTTable_OS_2_V3.h550
-rw-r--r--src/sfnt/SkOTTable_OS_2_V4.h585
-rw-r--r--src/sfnt/SkOTTable_OS_2_VA.h142
-rw-r--r--src/sfnt/SkOTTable_glyf.h214
-rw-r--r--src/sfnt/SkOTTable_loca.h31
-rw-r--r--src/sfnt/SkOTTable_maxp.h34
-rw-r--r--src/sfnt/SkOTTable_maxp_CFF.h30
-rw-r--r--src/sfnt/SkOTTable_maxp_TT.h49
-rw-r--r--src/sfnt/SkOTUtils.cpp161
-rw-r--r--src/sfnt/SkOTUtils.h37
-rw-r--r--src/sfnt/SkSFNTHeader.h70
-rw-r--r--src/utils/SkBitmapChecksummer.cpp69
-rw-r--r--src/utils/SkBitmapChecksummer.h37
-rw-r--r--src/utils/SkBitmapTransformer.cpp87
-rw-r--r--src/utils/SkBitmapTransformer.h104
-rw-r--r--src/utils/SkCityHash.cpp23
-rw-r--r--src/utils/SkCityHash.h47
-rw-r--r--src/utils/SkCondVar.cpp68
-rw-r--r--src/utils/SkCountdown.cpp33
-rw-r--r--src/utils/SkDeferredCanvas.cpp141
-rw-r--r--src/utils/SkDumpCanvas.cpp46
-rw-r--r--src/utils/SkMatrix44.cpp687
-rw-r--r--src/utils/SkNWayCanvas.cpp22
-rw-r--r--src/utils/SkPictureUtils.cpp213
-rw-r--r--src/utils/SkProxyCanvas.cpp12
-rw-r--r--src/utils/SkThreadPool.cpp88
-rw-r--r--src/utils/android/ashmem.c82
-rw-r--r--src/utils/android/ashmem.h43
-rw-r--r--src/utils/cityhash/README2
-rw-r--r--src/utils/cityhash/config.h17
-rw-r--r--src/utils/mac/SkCreateCGImageRef.cpp241
-rw-r--r--src/views/win/skia_win.cpp205
-rw-r--r--tests/Android.mk12
-rw-r--r--tests/AnnotationTest.cpp2
-rw-r--r--tests/BitmapFactoryTest.cpp76
-rw-r--r--tests/BitmapHeapTest.cpp96
-rw-r--r--tests/BitmapTransformerTest.cpp97
-rw-r--r--tests/CanvasTest.cpp40
-rw-r--r--tests/ChecksumTest.cpp185
-rw-r--r--tests/ClipCacheTest.cpp11
-rw-r--r--tests/ClipCubicTest.cpp2
-rw-r--r--tests/ClipStackTest.cpp478
-rw-r--r--tests/DeferredCanvasTest.cpp101
-rw-r--r--tests/DrawBitmapRectTest.cpp2
-rw-r--r--tests/DrawPathTest.cpp46
-rw-r--r--tests/EmptyPathTest.cpp2
-rw-r--r--tests/GLProgramsTest.cpp100
-rw-r--r--tests/GpuBitmapCopyTest.cpp165
-rw-r--r--tests/InfRectTest.cpp34
-rw-r--r--tests/LListTest.cpp321
-rw-r--r--tests/Matrix44Test.cpp315
-rw-r--r--tests/MatrixTest.cpp3
-rw-r--r--tests/PathTest.cpp745
-rw-r--r--tests/PictureTest.cpp309
-rw-r--r--tests/PipeTest.cpp2
-rw-r--r--tests/QuickRejectTest.cpp4
-rw-r--r--tests/RegionTest.cpp68
-rw-r--r--tests/RoundRectTest.cpp308
-rw-r--r--tests/SortTest.cpp28
-rw-r--r--tests/StringTest.cpp6
-rw-r--r--tests/StrokeTest.cpp64
-rw-r--r--tests/TDLinkedListTest.cpp89
-rw-r--r--tests/TLSTest.cpp34
-rw-r--r--tests/Test.cpp23
-rw-r--r--tests/Test.h1
-rw-r--r--tests/TileGridTest.cpp135
-rw-r--r--tests/skia_test.cpp57
-rw-r--r--tools/CopyTilesRenderer.cpp90
-rw-r--r--tools/CopyTilesRenderer.h46
-rw-r--r--tools/PictureBenchmark.cpp146
-rw-r--r--tools/PictureBenchmark.h88
-rw-r--r--tools/PictureRenderer.cpp833
-rw-r--r--tools/PictureRenderer.h457
-rw-r--r--tools/__init__.py0
-rw-r--r--tools/bench_pictures.cfg117
-rw-r--r--tools/bench_pictures_cfg_helper.py101
-rw-r--r--tools/bench_pictures_main.cpp805
-rw-r--r--tools/filtermain.cpp320
-rw-r--r--tools/path_utils.cpp134
-rw-r--r--tools/path_utils.h47
-rw-r--r--tools/picture_utils.cpp93
-rwxr-xr-xtools/rebaseline.py77
-rw-r--r--tools/render_pdfs_main.cpp219
-rw-r--r--tools/render_pictures_main.cpp714
-rw-r--r--tools/skdiff.cpp221
-rw-r--r--tools/skdiff.h265
-rw-r--r--tools/skdiff_html.cpp300
-rw-r--r--tools/skdiff_html.h21
-rw-r--r--tools/skdiff_image.cpp375
-rw-r--r--tools/skdiff_main.cpp773
-rw-r--r--tools/skdiff_utils.cpp186
-rw-r--r--tools/skdiff_utils.h53
-rw-r--r--tools/tests/bench_pictures_cfg_test.py46
-rwxr-xr-xtools/tests/run.sh70
-rw-r--r--tools/tests/skdiff/identical-bits-or-pixels/output-expected/stdout14
-rw-r--r--tools/tests/skdiff/identical-bits/output-expected/stdout14
-rw-r--r--tools/tests/skdiff/test1/output-expected/index.html50
-rw-r--r--tools/tests/skdiff/test1/output-expected/stdout27
-rw-r--r--tools/tests/skdiff/test2/output-expected/command_line1
-rw-r--r--tools/tests/skdiff/test2/output-expected/stdout21
642 files changed, 71661 insertions, 11079 deletions
diff --git a/Android.mk b/Android.mk
index 029b5d5805..2dc4b799e6 100644
--- a/Android.mk
+++ b/Android.mk
@@ -166,6 +166,7 @@ LOCAL_SRC_FILES:= \
src/core/SkRefDict.cpp \
src/core/SkRegion.cpp \
src/core/SkRegion_path.cpp \
+ src/core/SkRRect.cpp \
src/core/SkRTree.cpp \
src/core/SkScalar.cpp \
src/core/SkScalerContext.cpp \
@@ -180,7 +181,10 @@ LOCAL_SRC_FILES:= \
src/core/SkStream.cpp \
src/core/SkString.cpp \
src/core/SkStroke.cpp \
+ src/core/SkStrokeRec.cpp \
src/core/SkStrokerPriv.cpp \
+ src/core/SkTileGrid.cpp \
+ src/core/SkTileGridPicture.cpp \
src/core/SkTLS.cpp \
src/core/SkTSearch.cpp \
src/core/SkTypeface.cpp \
@@ -215,7 +219,9 @@ LOCAL_SRC_FILES:= \
src/effects/SkLightingImageFilter.cpp \
src/effects/SkMagnifierImageFilter.cpp \
src/effects/SkMatrixConvolutionImageFilter.cpp \
+ src/effects/SkMergeImageFilter.cpp \
src/effects/SkMorphologyImageFilter.cpp \
+ src/effects/SkOffsetImageFilter.cpp \
src/effects/SkPaintFlagsDrawFilter.cpp \
src/effects/SkPixelXorXfermode.cpp \
src/effects/SkPorterDuff.cpp \
@@ -243,6 +249,7 @@ LOCAL_SRC_FILES:= \
src/image/SkSurface_Picture.cpp \
src/image/SkSurface_Raster.cpp \
src/images/bmpdecoderhelper.cpp \
+ src/images/SkBitmapFactory.cpp \
src/images/SkBitmapRegionDecoder.cpp \
src/images/SkFDStream.cpp \
src/images/SkFlipPixelRef.cpp \
@@ -273,7 +280,7 @@ LOCAL_SRC_FILES:= \
src/ports/SkFontDescriptor.cpp \
src/ports/SkGlobalInitialization_default.cpp \
src/ports/SkFontHost_FreeType.cpp \
- src/ports/SkFontHost_FreeType_common.cpp \
+ src/ports/SkFontHost_FreeType_common.cpp \
src/ports/SkFontHost_sandbox_none.cpp \
src/ports/SkFontHost_android.cpp \
src/ports/SkFontHost_tables.cpp \
@@ -283,6 +290,7 @@ LOCAL_SRC_FILES:= \
src/ports/SkThread_pthread.cpp \
src/ports/SkTime_Unix.cpp \
src/utils/SkBase64.cpp \
+ src/utils/SkBitmapTransformer.cpp \
src/utils/SkBitSet.cpp \
src/utils/SkBoundaryPatch.cpp \
src/utils/SkCamera.cpp \
@@ -300,11 +308,15 @@ LOCAL_SRC_FILES:= \
src/utils/SkParse.cpp \
src/utils/SkParseColor.cpp \
src/utils/SkParsePath.cpp \
+ src/utils/SkPictureUtils.cpp \
src/utils/SkProxyCanvas.cpp \
src/utils/SkThreadUtils_pthread.cpp \
src/utils/SkThreadUtils_pthread_other.cpp \
src/utils/SkUnitMappers.cpp
+# src/utils/SkBitmapChecksummer.cpp \
+# src/utils/SkCityHash.cpp \
+
# maps to the 'skgr' gyp target
LOCAL_SRC_FILES += \
src/gpu/SkGpuDevice.cpp \
@@ -329,17 +341,16 @@ LOCAL_SRC_FILES += \
src/gpu/GrCacheID.cpp \
src/gpu/GrClipData.cpp \
src/gpu/GrContext.cpp \
- src/gpu/GrCustomStage.cpp \
src/gpu/GrDefaultPathRenderer.cpp \
src/gpu/GrDrawState.cpp \
src/gpu/GrDrawTarget.cpp \
+ src/gpu/GrEffect.cpp \
src/gpu/GrGeometryBuffer.cpp \
src/gpu/GrClipMaskCache.cpp \
src/gpu/GrClipMaskManager.cpp \
src/gpu/GrGpu.cpp \
src/gpu/GrGpuFactory.cpp \
src/gpu/GrInOrderDrawBuffer.cpp \
- src/gpu/GrMatrix.cpp \
src/gpu/GrMemory.cpp \
src/gpu/GrMemoryPool.cpp \
src/gpu/GrPath.cpp \
@@ -347,6 +358,7 @@ LOCAL_SRC_FILES += \
src/gpu/GrPathRenderer.cpp \
src/gpu/GrPathUtils.cpp \
src/gpu/GrRectanizer.cpp \
+ src/gpu/GrReducedClip.cpp \
src/gpu/GrRenderTarget.cpp \
src/gpu/GrResource.cpp \
src/gpu/GrResourceCache.cpp \
@@ -362,7 +374,6 @@ LOCAL_SRC_FILES += \
src/gpu/GrTextureAccess.cpp \
src/gpu/gr_unittests.cpp \
src/gpu/effects/GrTextureStripAtlas.cpp \
- src/gpu/effects/GrColorTableEffect.cpp \
src/gpu/effects/GrConfigConversionEffect.cpp \
src/gpu/effects/GrConvolutionEffect.cpp \
src/gpu/effects/GrSingleTextureEffect.cpp \
@@ -371,11 +382,12 @@ LOCAL_SRC_FILES += \
src/gpu/gl/GrGLContextInfo.cpp \
src/gpu/gl/GrGLCreateNullInterface.cpp \
src/gpu/gl/GrGLDefaultInterface_native.cpp \
+ src/gpu/gl/GrGLEffect.cpp \
+ src/gpu/gl/GrGLEffectMatrix.cpp \
src/gpu/gl/GrGLIndexBuffer.cpp \
src/gpu/gl/GrGLInterface.cpp \
src/gpu/gl/GrGLPath.cpp \
src/gpu/gl/GrGLProgram.cpp \
- src/gpu/gl/GrGLProgramStage.cpp \
src/gpu/gl/GrGLRenderTarget.cpp \
src/gpu/gl/GrGLShaderBuilder.cpp \
src/gpu/gl/GrGLSL.cpp \
@@ -492,7 +504,7 @@ include $(BUILD_SHARED_LIBRARY)
include $(BASE_PATH)/bench/Android.mk
# golden-master (fidelity / regression test)
-include $(BASE_PATH)/gm/Android.mk
+#include $(BASE_PATH)/gm/Android.mk
# unit-tests
include $(BASE_PATH)/tests/Android.mk
diff --git a/bench/Android.mk b/bench/Android.mk
index b304015c6c..6450c934a2 100644
--- a/bench/Android.mk
+++ b/bench/Android.mk
@@ -25,7 +25,9 @@ LOCAL_SRC_FILES += \
GradientBench.cpp \
GrMemoryPoolBench.cpp \
InterpBench.cpp \
+ LineBench.cpp \
MathBench.cpp \
+ Matrix44Bench.cpp \
MatrixBench.cpp \
MatrixConvolutionBench.cpp \
MemoryBench.cpp \
@@ -37,6 +39,7 @@ LOCAL_SRC_FILES += \
PictureRecordBench.cpp \
ReadPixBench.cpp \
RectBench.cpp \
+ RefCntBench.cpp \
RegionBench.cpp \
RepeatTileBench.cpp \
RTreeBench.cpp \
@@ -47,10 +50,6 @@ LOCAL_SRC_FILES += \
VertBench.cpp \
WriterBench.cpp
-# TODO: tests that currently are causing build problems
-LOCAL_SRC_FILES += \
- RefCntBench.cpp
-
LOCAL_SHARED_LIBRARIES := libcutils libskia libGLESv2 libEGL
LOCAL_STATIC_LIBRARIES := libstlport_static
diff --git a/bench/BitmapRectBench.cpp b/bench/BitmapRectBench.cpp
index b9e71f753b..4391626d89 100644
--- a/bench/BitmapRectBench.cpp
+++ b/bench/BitmapRectBench.cpp
@@ -59,8 +59,8 @@ public:
fBitmap.eraseColor(SK_ColorBLACK);
drawIntoBitmap(fBitmap);
- fSrcR.set(0, 0, w, h);
- fDstR.set(0, 0, w, h);
+ fSrcR.iset(0, 0, w, h);
+ fDstR.iset(0, 0, w, h);
}
protected:
diff --git a/bench/BlurBench.cpp b/bench/BlurBench.cpp
index 371e26db41..ab77d2b011 100644
--- a/bench/BlurBench.cpp
+++ b/bench/BlurBench.cpp
@@ -16,6 +16,7 @@
#define SMALL SkIntToScalar(2)
#define REAL SkFloatToScalar(1.5f)
#define BIG SkIntToScalar(10)
+#define REALBIG SkFloatToScalar(100.5f)
static const char* gStyleName[] = {
"normal",
@@ -27,17 +28,20 @@ static const char* gStyleName[] = {
class BlurBench : public SkBenchmark {
SkScalar fRadius;
SkBlurMaskFilter::BlurStyle fStyle;
+ uint32_t fFlags;
SkString fName;
public:
- BlurBench(void* param, SkScalar rad, SkBlurMaskFilter::BlurStyle bs) : INHERITED(param) {
+ BlurBench(void* param, SkScalar rad, SkBlurMaskFilter::BlurStyle bs, uint32_t flags = 0) : INHERITED(param) {
fRadius = rad;
fStyle = bs;
+ fFlags = flags;
const char* name = rad > 0 ? gStyleName[bs] : "none";
+ const char* quality = flags & SkBlurMaskFilter::kHighQuality_BlurFlag ? "high_quality" : "low_quality";
if (SkScalarFraction(rad) != 0) {
- fName.printf("blur_%.2f_%s", SkScalarToFloat(rad), name);
+ fName.printf("blur_%.2f_%s_%s", SkScalarToFloat(rad), name, quality);
} else {
- fName.printf("blur_%d_%s", SkScalarRound(rad), name);
+ fName.printf("blur_%d_%s_%s", SkScalarRound(rad), name, quality);
}
}
@@ -59,7 +63,7 @@ protected:
r.offset(fRadius, fRadius);
if (fRadius > 0) {
- SkMaskFilter* mf = SkBlurMaskFilter::Create(fRadius, fStyle, 0);
+ SkMaskFilter* mf = SkBlurMaskFilter::Create(fRadius, fStyle, fFlags);
paint.setMaskFilter(mf)->unref();
}
canvas->drawOval(r, paint);
@@ -70,37 +74,33 @@ private:
typedef SkBenchmark INHERITED;
};
-static SkBenchmark* Fact00(void* p) { return new BlurBench(p, SMALL, SkBlurMaskFilter::kNormal_BlurStyle); }
-static SkBenchmark* Fact01(void* p) { return new BlurBench(p, SMALL, SkBlurMaskFilter::kSolid_BlurStyle); }
-static SkBenchmark* Fact02(void* p) { return new BlurBench(p, SMALL, SkBlurMaskFilter::kOuter_BlurStyle); }
-static SkBenchmark* Fact03(void* p) { return new BlurBench(p, SMALL, SkBlurMaskFilter::kInner_BlurStyle); }
+DEF_BENCH(return new BlurBench(p, SMALL, SkBlurMaskFilter::kNormal_BlurStyle);)
+DEF_BENCH(return new BlurBench(p, SMALL, SkBlurMaskFilter::kSolid_BlurStyle);)
+DEF_BENCH(return new BlurBench(p, SMALL, SkBlurMaskFilter::kOuter_BlurStyle);)
+DEF_BENCH(return new BlurBench(p, SMALL, SkBlurMaskFilter::kInner_BlurStyle);)
-static SkBenchmark* Fact10(void* p) { return new BlurBench(p, BIG, SkBlurMaskFilter::kNormal_BlurStyle); }
-static SkBenchmark* Fact11(void* p) { return new BlurBench(p, BIG, SkBlurMaskFilter::kSolid_BlurStyle); }
-static SkBenchmark* Fact12(void* p) { return new BlurBench(p, BIG, SkBlurMaskFilter::kOuter_BlurStyle); }
-static SkBenchmark* Fact13(void* p) { return new BlurBench(p, BIG, SkBlurMaskFilter::kInner_BlurStyle); }
+DEF_BENCH(return new BlurBench(p, BIG, SkBlurMaskFilter::kNormal_BlurStyle);)
+DEF_BENCH(return new BlurBench(p, BIG, SkBlurMaskFilter::kSolid_BlurStyle);)
+DEF_BENCH(return new BlurBench(p, BIG, SkBlurMaskFilter::kOuter_BlurStyle);)
+DEF_BENCH(return new BlurBench(p, BIG, SkBlurMaskFilter::kInner_BlurStyle);)
-static SkBenchmark* Fact20(void* p) { return new BlurBench(p, REAL, SkBlurMaskFilter::kNormal_BlurStyle); }
-static SkBenchmark* Fact21(void* p) { return new BlurBench(p, REAL, SkBlurMaskFilter::kSolid_BlurStyle); }
-static SkBenchmark* Fact22(void* p) { return new BlurBench(p, REAL, SkBlurMaskFilter::kOuter_BlurStyle); }
-static SkBenchmark* Fact23(void* p) { return new BlurBench(p, REAL, SkBlurMaskFilter::kInner_BlurStyle); }
+DEF_BENCH(return new BlurBench(p, REALBIG, SkBlurMaskFilter::kNormal_BlurStyle);)
+DEF_BENCH(return new BlurBench(p, REALBIG, SkBlurMaskFilter::kSolid_BlurStyle);)
+DEF_BENCH(return new BlurBench(p, REALBIG, SkBlurMaskFilter::kOuter_BlurStyle);)
+DEF_BENCH(return new BlurBench(p, REALBIG, SkBlurMaskFilter::kInner_BlurStyle);)
-static SkBenchmark* FactNone(void* p) { return new BlurBench(p, 0, SkBlurMaskFilter::kNormal_BlurStyle); }
+DEF_BENCH(return new BlurBench(p, REAL, SkBlurMaskFilter::kNormal_BlurStyle);)
+DEF_BENCH(return new BlurBench(p, REAL, SkBlurMaskFilter::kSolid_BlurStyle);)
+DEF_BENCH(return new BlurBench(p, REAL, SkBlurMaskFilter::kOuter_BlurStyle);)
+DEF_BENCH(return new BlurBench(p, REAL, SkBlurMaskFilter::kInner_BlurStyle);)
-static BenchRegistry gReg00(Fact00);
-static BenchRegistry gReg01(Fact01);
-static BenchRegistry gReg02(Fact02);
-static BenchRegistry gReg03(Fact03);
+DEF_BENCH(return new BlurBench(p, SMALL, SkBlurMaskFilter::kNormal_BlurStyle, SkBlurMaskFilter::kHighQuality_BlurFlag);)
-static BenchRegistry gReg10(Fact10);
-static BenchRegistry gReg11(Fact11);
-static BenchRegistry gReg12(Fact12);
-static BenchRegistry gReg13(Fact13);
+DEF_BENCH(return new BlurBench(p, BIG, SkBlurMaskFilter::kNormal_BlurStyle, SkBlurMaskFilter::kHighQuality_BlurFlag);)
-static BenchRegistry gReg20(Fact20);
-static BenchRegistry gReg21(Fact21);
-static BenchRegistry gReg22(Fact22);
-static BenchRegistry gReg23(Fact23);
+DEF_BENCH(return new BlurBench(p, REALBIG, SkBlurMaskFilter::kNormal_BlurStyle, SkBlurMaskFilter::kHighQuality_BlurFlag);)
-static BenchRegistry gRegNone(FactNone);
+DEF_BENCH(return new BlurBench(p, REAL, SkBlurMaskFilter::kNormal_BlurStyle, SkBlurMaskFilter::kHighQuality_BlurFlag);)
+
+DEF_BENCH(return new BlurBench(p, 0, SkBlurMaskFilter::kNormal_BlurStyle);)
diff --git a/bench/DashBench.cpp b/bench/DashBench.cpp
index d766eb1cb0..b324cf2bc3 100644
--- a/bench/DashBench.cpp
+++ b/bench/DashBench.cpp
@@ -220,7 +220,6 @@ private:
*/
class DashLineBench : public SkBenchmark {
SkString fName;
- SkPath fPath;
SkScalar fStrokeWidth;
bool fIsRound;
SkAutoTUnref<SkPathEffect> fPE;
@@ -260,6 +259,58 @@ private:
typedef SkBenchmark INHERITED;
};
+class DrawPointsDashingBench : public SkBenchmark {
+ SkString fName;
+ int fStrokeWidth;
+ bool fDoAA;
+
+ SkAutoTUnref<SkPathEffect> fPathEffect;
+
+ enum {
+ N = SkBENCHLOOP(480)
+ };
+
+public:
+ DrawPointsDashingBench(void* param, int dashLength, int strokeWidth, bool doAA)
+ : INHERITED(param) {
+ fName.printf("drawpointsdash_%d_%d%s", dashLength, strokeWidth, doAA ? "_aa" : "_bw");
+ fStrokeWidth = strokeWidth;
+ fDoAA = doAA;
+
+ SkScalar vals[] = { SkIntToScalar(dashLength), SkIntToScalar(dashLength) };
+ fPathEffect.reset(new SkDashPathEffect(vals, 2, SK_Scalar1, false));
+ }
+
+protected:
+ virtual const char* onGetName() SK_OVERRIDE {
+ return fName.c_str();
+ }
+
+ virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE {
+
+ SkPaint p;
+ this->setupPaint(&p);
+ p.setColor(SK_ColorBLACK);
+ p.setStyle(SkPaint::kStroke_Style);
+ p.setStrokeWidth(SkIntToScalar(fStrokeWidth));
+ p.setPathEffect(fPathEffect);
+ p.setAntiAlias(fDoAA);
+
+ SkPoint pts[2] = {
+ { SkIntToScalar(10), 0 },
+ { SkIntToScalar(640), 0 }
+ };
+
+ for (int i = 0; i < N; ++i) {
+
+ pts[0].fY = pts[1].fY = SkIntToScalar(i % 480);
+ canvas->drawPoints(SkCanvas::kLines_PointMode, 2, pts, p);
+ }
+ }
+
+private:
+ typedef SkBenchmark INHERITED;
+};
///////////////////////////////////////////////////////////////////////////////
static const SkScalar gDots[] = { SK_Scalar1, SK_Scalar1 };
@@ -280,6 +331,13 @@ static SkBenchmark* gF701(void* p) { return new DashLineBench(p, 0, true); }
static SkBenchmark* gF711(void* p) { return new DashLineBench(p, SK_Scalar1, true); }
static SkBenchmark* gF721(void* p) { return new DashLineBench(p, 2 * SK_Scalar1, true); }
+static SkBenchmark* gF8(void* p) { return new DrawPointsDashingBench(p, 1, 1, false); }
+static SkBenchmark* gF9(void* p) { return new DrawPointsDashingBench(p, 1, 1, true); }
+static SkBenchmark* gF10(void* p) { return new DrawPointsDashingBench(p, 3, 1, false); }
+static SkBenchmark* gF11(void* p) { return new DrawPointsDashingBench(p, 3, 1, true); }
+static SkBenchmark* gF12(void* p) { return new DrawPointsDashingBench(p, 5, 5, false); }
+static SkBenchmark* gF13(void* p) { return new DrawPointsDashingBench(p, 5, 5, true); }
+
static BenchRegistry gR0(gF0);
static BenchRegistry gR1(gF1);
static BenchRegistry gR2(gF2);
@@ -293,3 +351,10 @@ static BenchRegistry gR720(gF720);
static BenchRegistry gR701(gF701);
static BenchRegistry gR711(gF711);
static BenchRegistry gR721(gF721);
+
+static BenchRegistry gR8(gF8);
+static BenchRegistry gR9(gF9);
+static BenchRegistry gR10(gF10);
+static BenchRegistry gR11(gF11);
+static BenchRegistry gR12(gF12);
+static BenchRegistry gR13(gF13);
diff --git a/bench/LineBench.cpp b/bench/LineBench.cpp
new file mode 100644
index 0000000000..feaae2b267
--- /dev/null
+++ b/bench/LineBench.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBenchmark.h"
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkColorPriv.h"
+#include "SkPaint.h"
+#include "SkRandom.h"
+#include "SkShader.h"
+#include "SkString.h"
+#include "SkTArray.h"
+
+
+class LineBench : public SkBenchmark {
+ SkScalar fStrokeWidth;
+ bool fDoAA;
+ SkString fName;
+ enum {
+ PTS = 500,
+ N = SkBENCHLOOP(10)
+ };
+ SkPoint fPts[PTS];
+
+public:
+ LineBench(void* param, SkScalar width, bool doAA) : INHERITED(param) {
+ fStrokeWidth = width;
+ fDoAA = doAA;
+ fName.printf("lines_%g_%s", width, doAA ? "AA" : "BW");
+
+ SkRandom rand;
+ for (int i = 0; i < PTS; ++i) {
+ fPts[i].set(rand.nextUScalar1() * 640, rand.nextUScalar1() * 480);
+ }
+ }
+
+protected:
+ virtual const char* onGetName() SK_OVERRIDE {
+ return fName.c_str();
+ }
+
+ virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE {
+ SkPaint paint;
+ this->setupPaint(&paint);
+
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setAntiAlias(fDoAA);
+ paint.setStrokeWidth(fStrokeWidth);
+
+ for (int i = 0; i < N; i++) {
+ canvas->drawPoints(SkCanvas::kLines_PointMode, PTS, fPts, paint);
+ }
+ }
+
+private:
+ typedef SkBenchmark INHERITED;
+};
+
+DEF_BENCH(return new LineBench(p, 0, false);)
+DEF_BENCH(return new LineBench(p, SK_Scalar1, false);)
+DEF_BENCH(return new LineBench(p, 0, true);)
+DEF_BENCH(return new LineBench(p, SK_Scalar1/2, true);)
+DEF_BENCH(return new LineBench(p, SK_Scalar1, true);)
diff --git a/bench/Matrix44Bench.cpp b/bench/Matrix44Bench.cpp
new file mode 100644
index 0000000000..31525d265f
--- /dev/null
+++ b/bench/Matrix44Bench.cpp
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBenchmark.h"
+#include "SkMatrix44.h"
+#include "SkRandom.h"
+#include "SkString.h"
+
+class Matrix44Bench : public SkBenchmark {
+ SkString fName;
+ enum { N = 10000 };
+public:
+ Matrix44Bench(void* param, const char name[]) : INHERITED(param) {
+ fName.printf("matrix44_%s", name);
+ fIsRendering = false;
+ }
+
+ virtual void performTest() = 0;
+
+protected:
+ virtual int mulLoopCount() const { return 1; }
+
+ virtual const char* onGetName() {
+ return fName.c_str();
+ }
+
+ virtual void onDraw(SkCanvas* canvas) {
+ int n = SkBENCHLOOP(N * this->mulLoopCount());
+ for (int i = 0; i < n; i++) {
+ this->performTest();
+ }
+ }
+
+private:
+ typedef SkBenchmark INHERITED;
+};
+
+class EqualsMatrix44Bench : public Matrix44Bench {
+public:
+ EqualsMatrix44Bench(void* param) : INHERITED(param, "equals") {
+ fM1.set(0, 0, 0);
+ fM2.set(3, 3, 0);
+ }
+protected:
+ virtual void performTest() {
+ for (int i = 0; i < 10; ++i) {
+ fM0 == fM1;
+ fM1 == fM2;
+ fM2 == fM0;
+ }
+ }
+private:
+ SkMatrix44 fM0, fM1, fM2;
+ typedef Matrix44Bench INHERITED;
+};
+
+class PreScaleMatrix44Bench : public Matrix44Bench {
+public:
+ PreScaleMatrix44Bench(void* param) : INHERITED(param, "prescale") {
+ fX = fY = fZ = SkDoubleToMScalar(1.5);
+ }
+protected:
+ virtual void performTest() {
+ fM0.reset();
+ for (int i = 0; i < 10; ++i) {
+ fM0.preScale(fX, fY, fZ);
+ }
+ }
+private:
+ SkMatrix44 fM0;
+ SkMScalar fX, fY, fZ;
+ typedef Matrix44Bench INHERITED;
+};
+
+class InvertMatrix44Bench : public Matrix44Bench {
+public:
+ InvertMatrix44Bench(void* param) : INHERITED(param, "invert") {
+ fM0.set(0, 0, -1.1);
+ fM0.set(0, 1, 2.1);
+ fM0.set(0, 2, -3.1);
+ fM0.set(0, 3, 4.1);
+ fM0.set(1, 0, 5.1);
+ fM0.set(1, 1, -6.1);
+ fM0.set(1, 2, 7.1);
+ fM0.set(1, 3, 8.1);
+ fM0.set(2, 0, -9.1);
+ fM0.set(2, 1, 10.1);
+ fM0.set(2, 2, 11.1);
+ fM0.set(2, 3, -12.1);
+ fM0.set(3, 0, -13.1);
+ fM0.set(3, 1, 14.1);
+ fM0.set(3, 2, -15.1);
+ fM0.set(3, 3, 16.1);
+ }
+protected:
+ virtual void performTest() {
+ for (int i = 0; i < 10; ++i) {
+ fM0.invert(&fM1);
+ }
+ }
+private:
+ SkMatrix44 fM0, fM1;
+ typedef Matrix44Bench INHERITED;
+};
+
+class PostScaleMatrix44Bench : public Matrix44Bench {
+public:
+ PostScaleMatrix44Bench(void* param) : INHERITED(param, "postscale") {
+ fX = fY = fZ = SkDoubleToMScalar(1.5);
+ }
+protected:
+ virtual void performTest() {
+ fM0.reset();
+ for (int i = 0; i < 10; ++i) {
+ fM0.postScale(fX, fY, fZ);
+ }
+ }
+private:
+ SkMatrix44 fM0;
+ SkMScalar fX, fY, fZ;
+ typedef Matrix44Bench INHERITED;
+};
+
+class SetConcatMatrix44Bench : public Matrix44Bench {
+public:
+ SetConcatMatrix44Bench(void* param) : INHERITED(param, "setconcat") {
+ fX = fY = fZ = SkDoubleToMScalar(1.5);
+ fM1.setScale(fX, fY, fZ);
+ fM2.setTranslate(fX, fY, fZ);
+ }
+protected:
+ virtual void performTest() {
+ fM0.reset(); // just to normalize this test with prescale/postscale
+ for (int i = 0; i < 10; ++i) {
+ fM0.setConcat(fM1, fM2);
+ }
+ }
+private:
+ SkMatrix44 fM0, fM1, fM2;
+ SkMScalar fX, fY, fZ;
+ typedef Matrix44Bench INHERITED;
+};
+
+class GetTypeMatrix44Bench : public Matrix44Bench {
+public:
+ GetTypeMatrix44Bench(void* param) : INHERITED(param, "gettype") {}
+protected:
+ // Putting random generation of the matrix inside performTest()
+ // would help us avoid anomalous runs, but takes up 25% or
+ // more of the function time.
+ virtual void performTest() {
+ for (int i = 0; i < 20; ++i) {
+ fMatrix.set(1, 2, 1); // to invalidate the type-cache
+ fMatrix.getType();
+ }
+ }
+private:
+ SkMatrix44 fMatrix;
+ typedef Matrix44Bench INHERITED;
+};
+
+DEF_BENCH( return new EqualsMatrix44Bench(p); )
+DEF_BENCH( return new PreScaleMatrix44Bench(p); )
+DEF_BENCH( return new PostScaleMatrix44Bench(p); )
+DEF_BENCH( return new InvertMatrix44Bench(p); )
+DEF_BENCH( return new SetConcatMatrix44Bench(p); )
+DEF_BENCH( return new GetTypeMatrix44Bench(p); )
+
diff --git a/bench/PathBench.cpp b/bench/PathBench.cpp
index 12e456ab06..390c5326ec 100644
--- a/bench/PathBench.cpp
+++ b/bench/PathBench.cpp
@@ -671,6 +671,198 @@ private:
typedef SkBenchmark INHERITED;
};
+// Chrome creates its own round rects with each corner possibly being different.
+// In its "zero radius" incarnation it creates degenerate round rects.
+// Note: PathTest::test_arb_round_rect_is_convex and
+// test_arb_zero_rad_round_rect_is_rect perform almost exactly
+// the same test (but with no drawing)
+class ArbRoundRectBench : public SkBenchmark {
+protected:
+ SkString fName;
+
+ enum {
+ N = SkBENCHLOOP(100)
+ };
+public:
+ ArbRoundRectBench(void* param, bool zeroRad) : INHERITED(param), fZeroRad(zeroRad) {
+ if (zeroRad) {
+ fName.printf("zeroradroundrect");
+ } else {
+ fName.printf("arbroundrect");
+ }
+ }
+
+protected:
+ virtual const char* onGetName() SK_OVERRIDE {
+ return fName.c_str();
+ }
+
+ static void add_corner_arc(SkPath* path, const SkRect& rect,
+ SkScalar xIn, SkScalar yIn,
+ int startAngle)
+ {
+
+ SkScalar rx = SkMinScalar(rect.width(), xIn);
+ SkScalar ry = SkMinScalar(rect.height(), yIn);
+
+ SkRect arcRect;
+ arcRect.set(-rx, -ry, rx, ry);
+ switch (startAngle) {
+ case 0:
+ arcRect.offset(rect.fRight - arcRect.fRight, rect.fBottom - arcRect.fBottom);
+ break;
+ case 90:
+ arcRect.offset(rect.fLeft - arcRect.fLeft, rect.fBottom - arcRect.fBottom);
+ break;
+ case 180:
+ arcRect.offset(rect.fLeft - arcRect.fLeft, rect.fTop - arcRect.fTop);
+ break;
+ case 270:
+ arcRect.offset(rect.fRight - arcRect.fRight, rect.fTop - arcRect.fTop);
+ break;
+ default:
+ break;
+ }
+
+ path->arcTo(arcRect, SkIntToScalar(startAngle), SkIntToScalar(90), false);
+ }
+
+ static void make_arb_round_rect(SkPath* path, const SkRect& r,
+ SkScalar xCorner, SkScalar yCorner) {
+ // we are lazy here and use the same x & y for each corner
+ add_corner_arc(path, r, xCorner, yCorner, 270);
+ add_corner_arc(path, r, xCorner, yCorner, 0);
+ add_corner_arc(path, r, xCorner, yCorner, 90);
+ add_corner_arc(path, r, xCorner, yCorner, 180);
+ path->close();
+
+#ifdef SK_REDEFINE_ROOT2OVER2_TO_MAKE_ARCTOS_CONVEX
+ SkASSERT(path->isConvex());
+#endif
+ }
+
+ virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE {
+ SkRandom rand;
+ SkRect r;
+
+ for (int i = 0; i < 5000; ++i) {
+ SkPaint paint;
+ paint.setColor(0xff000000 | rand.nextU());
+ paint.setAntiAlias(true);
+
+ SkScalar size = rand.nextUScalar1() * 30;
+ if (size < SK_Scalar1) {
+ continue;
+ }
+ r.fLeft = rand.nextUScalar1() * 300;
+ r.fTop = rand.nextUScalar1() * 300;
+ r.fRight = r.fLeft + 2 * size;
+ r.fBottom = r.fTop + 2 * size;
+
+ SkPath temp;
+
+ if (fZeroRad) {
+ make_arb_round_rect(&temp, r, 0, 0);
+
+ SkASSERT(temp.isRect(NULL));
+ } else {
+ make_arb_round_rect(&temp, r, r.width() / 10, r.height() / 15);
+ }
+
+ canvas->drawPath(temp, paint);
+ }
+ }
+
+private:
+ bool fZeroRad; // should 0 radius rounds rects be tested?
+
+ typedef SkBenchmark INHERITED;
+};
+
+class ConservativelyContainsBench : public SkBenchmark {
+public:
+ enum Type {
+ kRect_Type,
+ kRoundRect_Type,
+ kOval_Type,
+ };
+
+ ConservativelyContainsBench(void* param, Type type) : INHERITED(param) {
+ fIsRendering = false;
+ fParity = false;
+ fName = "conservatively_contains_";
+ switch (type) {
+ case kRect_Type:
+ fName.append("rect");
+ fPath.addRect(kBaseRect);
+ break;
+ case kRoundRect_Type:
+ fName.append("round_rect");
+ fPath.addRoundRect(kBaseRect, kRRRadii[0], kRRRadii[1]);
+ break;
+ case kOval_Type:
+ fName.append("oval");
+ fPath.addOval(kBaseRect);
+ break;
+ }
+ }
+
+private:
+ virtual const char* onGetName() SK_OVERRIDE {
+ return fName.c_str();
+ }
+
+ virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE {
+ for (int i = 0; i < N; ++i) {
+ const SkRect& rect = fQueryRects[i % kQueryRectCnt];
+ fParity = fParity != fPath.conservativelyContainsRect(rect);
+ }
+ }
+
+ virtual void onPreDraw() SK_OVERRIDE {
+ fQueryRects.setCount(kQueryRectCnt);
+
+ SkRandom rand;
+ for (int i = 0; i < kQueryRectCnt; ++i) {
+ SkSize size;
+ SkPoint xy;
+ size.fWidth = rand.nextRangeScalar(kQueryMin.fWidth, kQueryMax.fWidth);
+ size.fHeight = rand.nextRangeScalar(kQueryMin.fHeight, kQueryMax.fHeight);
+ xy.fX = rand.nextRangeScalar(kBounds.fLeft, kBounds.fRight - size.fWidth);
+ xy.fY = rand.nextRangeScalar(kBounds.fTop, kBounds.fBottom - size.fHeight);
+
+ fQueryRects[i] = SkRect::MakeXYWH(xy.fX, xy.fY, size.fWidth, size.fHeight);
+ }
+ }
+
+ virtual void onPostDraw() SK_OVERRIDE {
+ fQueryRects.setCount(0);
+ }
+
+ enum {
+ N = SkBENCHLOOP(100000),
+ kQueryRectCnt = 400,
+ };
+ static const SkRect kBounds; // bounds for all random query rects
+ static const SkSize kQueryMin; // minimum query rect size, should be <= kQueryMax
+ static const SkSize kQueryMax; // max query rect size, should < kBounds
+ static const SkRect kBaseRect; // rect that is used to construct the path
+ static const SkScalar kRRRadii[2]; // x and y radii for round rect
+
+ SkString fName;
+ SkPath fPath;
+ bool fParity;
+ SkTDArray<SkRect> fQueryRects;
+
+ typedef SkBenchmark INHERITED;
+};
+
+const SkRect ConservativelyContainsBench::kBounds = SkRect::MakeWH(SkIntToScalar(100), SkIntToScalar(100));
+const SkSize ConservativelyContainsBench::kQueryMin = SkSize::Make(SkIntToScalar(1), SkIntToScalar(1));
+const SkSize ConservativelyContainsBench::kQueryMax = SkSize::Make(SkIntToScalar(40), SkIntToScalar(40));
+const SkRect ConservativelyContainsBench::kBaseRect = SkRect::MakeXYWH(SkIntToScalar(25), SkIntToScalar(25), SkIntToScalar(50), SkIntToScalar(50));
+const SkScalar ConservativelyContainsBench::kRRRadii[2] = {SkIntToScalar(5), SkIntToScalar(10)};
+
static SkBenchmark* FactT00(void* p) { return new TrianglePathBench(p, FLAGS00); }
static SkBenchmark* FactT01(void* p) { return new TrianglePathBench(p, FLAGS01); }
static SkBenchmark* FactT10(void* p) { return new TrianglePathBench(p, FLAGS10); }
@@ -770,3 +962,17 @@ static BenchRegistry gRegReverseTo(FactReverseTo);
static SkBenchmark* CirclesTest(void* p) { return new CirclesBench(p); }
static BenchRegistry gRegCirclesTest(CirclesTest);
+static SkBenchmark* ArbRoundRectTest(void* p) { return new ArbRoundRectBench(p, false); }
+static BenchRegistry gRegArbRoundRectTest(ArbRoundRectTest);
+
+static SkBenchmark* ZeroRadRoundRectTest(void* p) { return new ArbRoundRectBench(p, true); }
+static BenchRegistry gRegZeroRadRoundRectTest(ZeroRadRoundRectTest);
+
+static SkBenchmark* RectConservativelyContainsTest(void* p) { return new ConservativelyContainsBench(p, ConservativelyContainsBench::kRect_Type); }
+static BenchRegistry gRegRectConservativelyContainsTest(RectConservativelyContainsTest);
+
+static SkBenchmark* RoundRectConservativelyContainsTest(void* p) { return new ConservativelyContainsBench(p, ConservativelyContainsBench::kRoundRect_Type); }
+static BenchRegistry gRegRoundRectConservativelyContainsTest(RoundRectConservativelyContainsTest);
+
+static SkBenchmark* OvalConservativelyContainsTest(void* p) { return new ConservativelyContainsBench(p, ConservativelyContainsBench::kOval_Type); }
+static BenchRegistry gRegOvalConservativelyContainsTest(OvalConservativelyContainsTest);
diff --git a/bench/RectBench.cpp b/bench/RectBench.cpp
index 992b8f9858..4a2d3c57bd 100644
--- a/bench/RectBench.cpp
+++ b/bench/RectBench.cpp
@@ -23,7 +23,10 @@ public:
SkRect fRects[N];
SkColor fColors[N];
- RectBench(void* param, int shift, int stroke = 0) : INHERITED(param), fShift(shift), fStroke(stroke) {
+ RectBench(void* param, int shift, int stroke = 0)
+ : INHERITED(param)
+ , fShift(shift)
+ , fStroke(stroke) {
SkRandom rand;
const SkScalar offset = SK_Scalar1/3;
for (int i = 0; i < N; i++) {
@@ -73,6 +76,37 @@ private:
typedef SkBenchmark INHERITED;
};
+class SrcModeRectBench : public RectBench {
+public:
+ SrcModeRectBench(void* param) : INHERITED(param, 1, 0) {
+ fMode = SkXfermode::Create(SkXfermode::kSrc_Mode);
+ }
+
+ virtual ~SrcModeRectBench() {
+ SkSafeUnref(fMode);
+ }
+
+protected:
+ virtual void setupPaint(SkPaint* paint) SK_OVERRIDE {
+ this->INHERITED::setupPaint(paint);
+ // srcmode is most interesting when we're not opaque
+ paint->setAlpha(0x80);
+ paint->setXfermode(fMode);
+ }
+
+ virtual const char* onGetName() SK_OVERRIDE {
+ fName.set(this->INHERITED::onGetName());
+ fName.prepend("srcmode_");
+ return fName.c_str();
+ }
+
+private:
+ SkString fName;
+ SkXfermode* fMode;
+
+ typedef RectBench INHERITED;
+};
+
class OvalBench : public RectBench {
public:
OvalBench(void* param, int shift) : RectBench(param, shift) {}
@@ -128,6 +162,40 @@ protected:
virtual const char* onGetName() { return fName; }
};
+class AARectBench : public SkBenchmark {
+public:
+ enum {
+ W = 640,
+ H = 480,
+ };
+
+ AARectBench(void* param) : INHERITED(param) {}
+
+protected:
+
+ virtual const char* onGetName() { return "aarects"; }
+
+ virtual void onDraw(SkCanvas* canvas) {
+ SkPaint paint;
+ this->setupPaint(&paint);
+ paint.setAntiAlias(true);
+ paint.setColor(SK_ColorBLACK);
+ SkRect r;
+
+ // Draw small aa rects in a grid across the screen
+ for (SkScalar y = SK_ScalarHalf; y < H; y += SkIntToScalar(2)) {
+ for (SkScalar x = SK_ScalarHalf; x < W; x += SkIntToScalar(2)) {
+ r.set(x, y,
+ x+SkFloatToScalar(1.5f), y+SkFloatToScalar(1.5f));
+ canvas->drawRect(r, paint);
+ }
+ }
+
+ }
+private:
+ typedef SkBenchmark INHERITED;
+};
+
/*******************************************************************************
* to bench BlitMask [Opaque, Black, color, shader]
*******************************************************************************/
@@ -207,63 +275,37 @@ private:
};
-static SkBenchmark* RectFactory1F(void* p) { return SkNEW_ARGS(RectBench, (p, 1)); }
-static SkBenchmark* RectFactory1S(void* p) { return SkNEW_ARGS(RectBench, (p, 1, 4)); }
-static SkBenchmark* RectFactory2F(void* p) { return SkNEW_ARGS(RectBench, (p, 3)); }
-static SkBenchmark* RectFactory2S(void* p) { return SkNEW_ARGS(RectBench, (p, 3, 4)); }
-static SkBenchmark* OvalFactory1(void* p) { return SkNEW_ARGS(OvalBench, (p, 1)); }
-static SkBenchmark* OvalFactory2(void* p) { return SkNEW_ARGS(OvalBench, (p, 3)); }
-static SkBenchmark* RRectFactory1(void* p) { return SkNEW_ARGS(RRectBench, (p, 1)); }
-static SkBenchmark* RRectFactory2(void* p) { return SkNEW_ARGS(RRectBench, (p, 3)); }
-static SkBenchmark* PointsFactory(void* p) {
- return SkNEW_ARGS(PointsBench, (p, SkCanvas::kPoints_PointMode, "points"));
-}
-static SkBenchmark* LinesFactory(void* p) {
- return SkNEW_ARGS(PointsBench, (p, SkCanvas::kLines_PointMode, "lines"));
-}
-static SkBenchmark* PolygonFactory(void* p) {
- return SkNEW_ARGS(PointsBench, (p, SkCanvas::kPolygon_PointMode, "polygon"));
-}
+DEF_BENCH( return SkNEW_ARGS(RectBench, (p, 1)); )
+DEF_BENCH( return SkNEW_ARGS(RectBench, (p, 1, 4)); )
+DEF_BENCH( return SkNEW_ARGS(RectBench, (p, 3)); )
+DEF_BENCH( return SkNEW_ARGS(RectBench, (p, 3, 4)); )
+DEF_BENCH( return SkNEW_ARGS(OvalBench, (p, 1)); )
+DEF_BENCH( return SkNEW_ARGS(OvalBench, (p, 3)); )
+DEF_BENCH( return SkNEW_ARGS(RRectBench, (p, 1)); )
+DEF_BENCH( return SkNEW_ARGS(RRectBench, (p, 3)); )
+DEF_BENCH( return SkNEW_ARGS(PointsBench, (p, SkCanvas::kPoints_PointMode, "points")); )
+DEF_BENCH( return SkNEW_ARGS(PointsBench, (p, SkCanvas::kLines_PointMode, "lines")); )
+DEF_BENCH( return SkNEW_ARGS(PointsBench, (p, SkCanvas::kPolygon_PointMode, "polygon")); )
+
+DEF_BENCH( return SkNEW_ARGS(SrcModeRectBench, (p)); )
+DEF_BENCH( return SkNEW_ARGS(AARectBench, (p)); )
/* init the blitmask bench
*/
-static SkBenchmark* BlitMaskOpaqueFactory(void* p) {
- return SkNEW_ARGS(BlitMaskBench,
+DEF_BENCH( return SkNEW_ARGS(BlitMaskBench,
(p, SkCanvas::kPoints_PointMode,
BlitMaskBench::kMaskOpaque, "maskopaque")
- );
-}
-static SkBenchmark* BlitMaskBlackFactory(void* p) {
- return SkNEW_ARGS(BlitMaskBench,
+ ); )
+DEF_BENCH( return SkNEW_ARGS(BlitMaskBench,
(p, SkCanvas::kPoints_PointMode,
BlitMaskBench::kMaskBlack, "maskblack")
- );
-}
-static SkBenchmark* BlitMaskColorFactory(void* p) {
- return SkNEW_ARGS(BlitMaskBench,
+ ); )
+DEF_BENCH( return SkNEW_ARGS(BlitMaskBench,
(p, SkCanvas::kPoints_PointMode,
BlitMaskBench::kMaskColor, "maskcolor")
- );
-}
-static SkBenchmark* BlitMaskShaderFactory(void* p) {
- return SkNEW_ARGS(BlitMaskBench,
+ ); )
+DEF_BENCH( return SkNEW_ARGS(BlitMaskBench,
(p, SkCanvas::kPoints_PointMode,
BlitMaskBench::KMaskShader, "maskshader")
- );
-}
-
-static BenchRegistry gRectReg1F(RectFactory1F);
-static BenchRegistry gRectReg1S(RectFactory1S);
-static BenchRegistry gRectReg2F(RectFactory2F);
-static BenchRegistry gRectReg2S(RectFactory2S);
-static BenchRegistry gOvalReg1(OvalFactory1);
-static BenchRegistry gOvalReg2(OvalFactory2);
-static BenchRegistry gRRectReg1(RRectFactory1);
-static BenchRegistry gRRectReg2(RRectFactory2);
-static BenchRegistry gPointsReg(PointsFactory);
-static BenchRegistry gLinesReg(LinesFactory);
-static BenchRegistry gPolygonReg(PolygonFactory);
-static BenchRegistry gRectRegOpaque(BlitMaskOpaqueFactory);
-static BenchRegistry gRectRegBlack(BlitMaskBlackFactory);
-static BenchRegistry gRectRegColor(BlitMaskColorFactory);
-static BenchRegistry gRectRegShader(BlitMaskShaderFactory);
+ ); )
+
diff --git a/bench/RepeatTileBench.cpp b/bench/RepeatTileBench.cpp
index da36205eaf..fe39da4185 100644
--- a/bench/RepeatTileBench.cpp
+++ b/bench/RepeatTileBench.cpp
@@ -88,7 +88,7 @@ class RepeatTileBench : public SkBenchmark {
SkString fName;
enum { N = SkBENCHLOOP(20) };
public:
- RepeatTileBench(void* param, SkBitmap::Config c) : INHERITED(param) {
+ RepeatTileBench(void* param, SkBitmap::Config c, bool isOpaque = false) : INHERITED(param) {
const int w = 50;
const int h = 50;
SkBitmap bm;
@@ -99,7 +99,8 @@ public:
bm.setConfig(c, w, h);
}
bm.allocPixels();
- bm.eraseColor(0);
+ bm.eraseColor(isOpaque ? SK_ColorWHITE : 0);
+ bm.setIsOpaque(isOpaque);
drawIntoBitmap(bm);
@@ -113,7 +114,7 @@ public:
SkShader::kRepeat_TileMode,
SkShader::kRepeat_TileMode);
fPaint.setShader(s)->unref();
- fName.printf("repeatTile_%s", gConfigName[bm.config()]);
+ fName.printf("repeatTile_%s_%c", gConfigName[bm.config()], isOpaque ? 'X' : 'A');
}
protected:
@@ -134,12 +135,9 @@ private:
typedef SkBenchmark INHERITED;
};
-static SkBenchmark* Fact0(void* p) { return new RepeatTileBench(p, SkBitmap::kARGB_8888_Config); }
-static SkBenchmark* Fact1(void* p) { return new RepeatTileBench(p, SkBitmap::kRGB_565_Config); }
-static SkBenchmark* Fact2(void* p) { return new RepeatTileBench(p, SkBitmap::kARGB_4444_Config); }
-static SkBenchmark* Fact3(void* p) { return new RepeatTileBench(p, SkBitmap::kIndex8_Config); }
+DEF_BENCH(return new RepeatTileBench(p, SkBitmap::kARGB_8888_Config, true))
+DEF_BENCH(return new RepeatTileBench(p, SkBitmap::kARGB_8888_Config, false))
+DEF_BENCH(return new RepeatTileBench(p, SkBitmap::kRGB_565_Config))
+DEF_BENCH(return new RepeatTileBench(p, SkBitmap::kARGB_4444_Config))
+DEF_BENCH(return new RepeatTileBench(p, SkBitmap::kIndex8_Config))
-static BenchRegistry gReg0(Fact0);
-static BenchRegistry gReg1(Fact1);
-static BenchRegistry gReg2(Fact2);
-static BenchRegistry gReg3(Fact3);
diff --git a/bench/SkBenchmark.h b/bench/SkBenchmark.h
index b72162f44c..001b3ab679 100644
--- a/bench/SkBenchmark.h
+++ b/bench/SkBenchmark.h
@@ -13,12 +13,6 @@
#include "SkTDict.h"
#include "SkTRegistry.h"
-
-#define SK_MACRO_CONCAT(X, Y) SK_MACRO_CONCAT_IMPL(X, Y)
-#define SK_MACRO_CONCAT_IMPL(X, Y) X ## Y
-
-#define SK_MACRO_APPEND_LINE(name) SK_MACRO_CONCAT(name, __LINE__)
-
#define DEF_BENCH(code) \
static SkBenchmark* SK_MACRO_APPEND_LINE(F_)(void* p) { code; } \
static BenchRegistry SK_MACRO_APPEND_LINE(R_)(SK_MACRO_APPEND_LINE(F_));
@@ -113,7 +107,7 @@ public:
bool findDefineScalar(const char* key, SkScalar* value) const;
protected:
- void setupPaint(SkPaint* paint);
+ virtual void setupPaint(SkPaint* paint);
virtual const char* onGetName() = 0;
virtual void onPreDraw() {}
diff --git a/bench/bench_expectations.txt b/bench/bench_expectations.txt
index 4c6f05fd5c..0bba96f12a 100644
--- a/bench/bench_expectations.txt
+++ b/bench/bench_expectations.txt
@@ -9,4 +9,2068 @@
# Note: unrecognized data lines (not exactly 4 commas, unparsable bench values)
# will likely raise exceptions.
-picture_playback_drawText_GPU_c,Mac_Float_NoDebug_64-25th,160,150,170
+# Microbench expectations.
+# Currently none.
+
+# SkPicture bench expectations. Default is generated by gen_skp_ranges.py on a
+# given revision. Prepend "#" to have alerting ignore the corresponding row.
+# The default was obtained with command:
+# bench/gen_skp_ranges.py --rev-range=6580:6585
+androidpolice.skp_copy_tiles_,Mac_Float_Bench_32-25th,656.000,547.600,731.600
+androidpolice.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,1031.970,867.174,1145.167
+androidpolice.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,232.440,187.574,265.684
+androidpolice.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+androidpolice.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.399,-9.661,10.439
+androidpolice.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.098,-9.916,10.108
+androidpolice.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,2.000,-8.300,12.200
+androidpolice.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,2.377,-7.980,12.614
+androidpolice.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.804,-9.316,10.885
+androidpolice.skp_record_,Mac_Float_Bench_32-25th,22.000,8.700,34.200
+androidpolice.skp_record_,Nexus10_4-1_Float_Bench_32-25th,24.244,10.607,36.668
+androidpolice.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,4.233,-6.402,14.657
+androidpolice.skp_record_grid_,Mac_Float_Bench_32-25th,25.000,11.250,37.500
+androidpolice.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,26.864,12.834,39.550
+androidpolice.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,5.255,-5.534,15.780
+androidpolice.skp_record_rtree_,Mac_Float_Bench_32-25th,26.000,12.100,38.600
+androidpolice.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,27.249,13.161,39.974
+androidpolice.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,5.923,-4.966,16.515
+androidpolice.skp_tile_1024x256_,Mac_Float_Bench_32-25th,630.000,525.500,703.000
+androidpolice.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,1008.890,847.556,1119.779
+androidpolice.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,243.830,197.256,278.213
+androidpolice.skp_tile_1024x64_,Mac_Float_Bench_32-25th,824.000,690.400,916.400
+androidpolice.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,1330.430,1120.866,1473.473
+androidpolice.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,333.880,273.798,377.268
+androidpolice.skp_tile_256x1024_,Mac_Float_Bench_32-25th,658.000,549.300,733.800
+androidpolice.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,970.030,814.525,1077.033
+androidpolice.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,260.460,211.391,296.506
+androidpolice.skp_tile_256x256_,Mac_Float_Bench_32-25th,741.000,619.850,825.100
+androidpolice.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,1152.620,969.727,1277.882
+androidpolice.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,307.510,251.383,348.261
+androidpolice.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,394.000,324.900,443.400
+androidpolice.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,2411.340,2039.639,2662.474
+androidpolice.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,333.680,273.628,377.048
+androidpolice.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,332.530,272.650,375.783
+androidpolice.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,655.000,546.750,730.500
+androidpolice.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,931.980,782.183,1035.178
+androidpolice.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,258.800,209.980,294.680
+androidpolice.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,439.000,363.150,492.900
+androidpolice.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,759.560,635.626,845.516
+androidpolice.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,195.370,156.065,224.907
+androidpolice.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,398.000,328.300,447.800
+androidpolice.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,759.110,635.244,845.021
+androidpolice.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,140.330,109.281,164.363
+androidpolice.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,400.000,330.000,450.000
+androidpolice.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,710.970,594.325,792.067
+androidpolice.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,133.100,103.135,156.410
+androidpolice.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,654.000,545.900,729.400
+androidpolice.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,897.650,753.002,997.415
+androidpolice.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,259.120,210.252,295.032
+androidpolice.skp_tile_512x512_,Mac_Float_Bench_32-25th,620.000,517.000,692.000
+androidpolice.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,962.260,807.921,1068.486
+androidpolice.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,244.730,198.020,279.203
+androidpolice.skp_tile_64x1024_,Mac_Float_Bench_32-25th,936.000,785.600,1039.600
+androidpolice.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,1401.770,1181.505,1551.947
+androidpolice.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,387.160,319.086,435.876
+br.337.skp_copy_tiles_,Mac_Float_Bench_32-25th,38.000,22.300,51.800
+br.337.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,56.810,38.288,72.491
+br.337.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,7.820,-3.353,18.602
+br.337.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+br.337.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.242,-9.794,10.266
+br.337.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.058,-9.951,10.063
+br.337.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+br.337.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,1.102,-9.063,11.213
+br.337.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.389,-9.670,10.427
+br.337.skp_record_,Mac_Float_Bench_32-25th,32.000,17.200,45.200
+br.337.skp_record_,Nexus10_4-1_Float_Bench_32-25th,35.067,19.807,48.574
+br.337.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,9.637,-1.808,20.601
+br.337.skp_record_grid_,Mac_Float_Bench_32-25th,33.000,18.050,46.300
+br.337.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,36.050,20.642,49.655
+br.337.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,10.136,-1.384,21.150
+br.337.skp_record_rtree_,Mac_Float_Bench_32-25th,33.000,18.050,46.300
+br.337.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,36.131,20.712,49.744
+br.337.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,10.299,-1.246,21.329
+br.337.skp_tile_1024x256_,Mac_Float_Bench_32-25th,19.000,6.150,30.900
+br.337.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,56.950,38.407,72.645
+br.337.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,9.840,-1.636,20.824
+br.337.skp_tile_1024x64_,Mac_Float_Bench_32-25th,32.000,17.200,45.200
+br.337.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,81.400,59.190,99.540
+br.337.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,16.840,4.314,28.524
+br.337.skp_tile_256x1024_,Mac_Float_Bench_32-25th,27.000,12.950,39.700
+br.337.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,76.520,55.042,94.172
+br.337.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,13.540,1.509,24.894
+br.337.skp_tile_256x256_,Mac_Float_Bench_32-25th,29.000,14.650,41.900
+br.337.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,85.020,62.267,103.522
+br.337.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,14.720,2.512,26.192
+br.337.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,65.000,45.250,81.500
+br.337.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,285.300,232.505,323.830
+br.337.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,42.290,25.947,56.519
+br.337.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,41.530,25.300,55.683
+br.337.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,16.000,3.600,27.600
+br.337.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,49.400,31.990,64.340
+br.337.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,7.910,-3.277,18.701
+br.337.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,16.000,3.600,27.600
+br.337.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,55.820,37.447,71.402
+br.337.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,8.250,-2.987,19.075
+br.337.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,18.000,5.300,29.800
+br.337.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,45.990,29.092,60.589
+br.337.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,7.670,-3.481,18.437
+br.337.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,16.000,3.600,27.600
+br.337.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,56.210,37.779,71.831
+br.337.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,5.980,-4.917,16.578
+br.337.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,17.000,4.450,28.700
+br.337.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,55.130,36.861,70.643
+br.337.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,8.560,-2.724,19.416
+br.337.skp_tile_512x512_,Mac_Float_Bench_32-25th,19.000,6.150,30.900
+br.337.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,63.160,43.686,79.476
+br.337.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,10.220,-1.313,21.242
+br.337.skp_tile_64x1024_,Mac_Float_Bench_32-25th,64.000,44.400,80.400
+br.337.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,169.320,133.922,196.252
+br.337.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,31.880,17.098,45.068
+cnn-michelle-obama-debate.skp_copy_tiles_,Mac_Float_Bench_32-25th,112.000,85.200,133.200
+cnn-michelle-obama-debate.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,127.490,98.366,150.239
+cnn-michelle-obama-debate.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,58.610,39.819,74.471
+cnn-michelle-obama-debate.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+cnn-michelle-obama-debate.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.337,-9.714,10.370
+cnn-michelle-obama-debate.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.063,-9.946,10.070
+cnn-michelle-obama-debate.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+cnn-michelle-obama-debate.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,1.246,-8.941,11.370
+cnn-michelle-obama-debate.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.399,-9.661,10.439
+cnn-michelle-obama-debate.skp_record_,Mac_Float_Bench_32-25th,7.000,-4.050,17.700
+cnn-michelle-obama-debate.skp_record_,Nexus10_4-1_Float_Bench_32-25th,11.457,-0.262,22.603
+cnn-michelle-obama-debate.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,2.659,-7.740,12.925
+cnn-michelle-obama-debate.skp_record_grid_,Mac_Float_Bench_32-25th,9.000,-2.350,19.900
+cnn-michelle-obama-debate.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,14.239,2.103,25.663
+cnn-michelle-obama-debate.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.227,-7.257,13.550
+cnn-michelle-obama-debate.skp_record_rtree_,Mac_Float_Bench_32-25th,9.000,-2.350,19.900
+cnn-michelle-obama-debate.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,15.477,3.156,27.025
+cnn-michelle-obama-debate.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.549,-6.983,13.904
+cnn-michelle-obama-debate.skp_tile_1024x256_,Mac_Float_Bench_32-25th,69.000,48.650,85.900
+cnn-michelle-obama-debate.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,141.460,110.241,165.606
+cnn-michelle-obama-debate.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,59.600,40.660,75.560
+cnn-michelle-obama-debate.skp_tile_1024x64_,Mac_Float_Bench_32-25th,128.000,98.800,150.800
+cnn-michelle-obama-debate.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,242.740,196.329,277.014
+cnn-michelle-obama-debate.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,94.340,70.189,113.774
+cnn-michelle-obama-debate.skp_tile_256x1024_,Mac_Float_Bench_32-25th,72.000,51.200,89.200
+cnn-michelle-obama-debate.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,175.180,138.903,202.698
+cnn-michelle-obama-debate.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,60.440,41.374,76.484
+cnn-michelle-obama-debate.skp_tile_256x256_,Mac_Float_Bench_32-25th,108.000,81.800,128.800
+cnn-michelle-obama-debate.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,222.590,179.202,254.849
+cnn-michelle-obama-debate.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,82.100,59.785,100.310
+cnn-michelle-obama-debate.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,305.000,249.250,345.500
+cnn-michelle-obama-debate.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,1483.870,1251.289,1642.257
+cnn-michelle-obama-debate.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,287.640,234.494,326.404
+cnn-michelle-obama-debate.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,286.430,233.465,325.073
+cnn-michelle-obama-debate.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,56.000,37.600,71.600
+cnn-michelle-obama-debate.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,91.350,67.647,110.485
+cnn-michelle-obama-debate.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,53.440,35.424,68.784
+cnn-michelle-obama-debate.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,61.000,41.850,77.100
+cnn-michelle-obama-debate.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,186.320,148.372,214.952
+cnn-michelle-obama-debate.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,55.130,36.861,70.643
+cnn-michelle-obama-debate.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,62.000,42.700,78.200
+cnn-michelle-obama-debate.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,172.930,136.990,200.223
+cnn-michelle-obama-debate.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,50.040,32.534,65.044
+cnn-michelle-obama-debate.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,61.000,41.850,77.100
+cnn-michelle-obama-debate.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,169.990,134.492,196.989
+cnn-michelle-obama-debate.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,55.900,37.515,71.490
+cnn-michelle-obama-debate.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,57.000,38.450,72.700
+cnn-michelle-obama-debate.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,93.740,69.679,113.114
+cnn-michelle-obama-debate.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,54.960,36.716,70.456
+cnn-michelle-obama-debate.skp_tile_512x512_,Mac_Float_Bench_32-25th,68.000,47.800,84.800
+cnn-michelle-obama-debate.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,150.050,117.543,175.055
+cnn-michelle-obama-debate.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,60.190,41.161,76.209
+cnn-michelle-obama-debate.skp_tile_64x1024_,Mac_Float_Bench_32-25th,146.000,114.100,170.600
+cnn-michelle-obama-debate.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,265.480,215.658,302.028
+cnn-michelle-obama-debate.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,101.770,76.504,121.947
+culturalsolutions.co.uk.skp_copy_tiles_,Mac_Float_Bench_32-25th,133.000,103.050,156.300
+culturalsolutions.co.uk.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,188.980,150.633,217.878
+culturalsolutions.co.uk.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,54.820,36.597,70.302
+culturalsolutions.co.uk.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+culturalsolutions.co.uk.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.174,-9.852,10.192
+culturalsolutions.co.uk.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.038,-9.968,10.042
+culturalsolutions.co.uk.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+culturalsolutions.co.uk.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,0.458,-9.611,10.503
+culturalsolutions.co.uk.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.146,-9.876,10.161
+culturalsolutions.co.uk.skp_record_,Mac_Float_Bench_32-25th,80.000,58.000,98.000
+culturalsolutions.co.uk.skp_record_,Nexus10_4-1_Float_Bench_32-25th,73.226,52.242,90.549
+culturalsolutions.co.uk.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,21.663,8.413,33.829
+culturalsolutions.co.uk.skp_record_grid_,Mac_Float_Bench_32-25th,80.000,58.000,98.000
+culturalsolutions.co.uk.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,77.016,55.463,94.717
+culturalsolutions.co.uk.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,22.095,8.781,34.305
+culturalsolutions.co.uk.skp_record_rtree_,Mac_Float_Bench_32-25th,81.000,58.850,99.100
+culturalsolutions.co.uk.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,72.397,51.538,89.637
+culturalsolutions.co.uk.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,22.067,8.757,34.273
+culturalsolutions.co.uk.skp_tile_1024x256_,Mac_Float_Bench_32-25th,86.000,63.100,104.600
+culturalsolutions.co.uk.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,194.710,155.504,224.181
+culturalsolutions.co.uk.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,50.050,32.542,65.055
+culturalsolutions.co.uk.skp_tile_1024x64_,Mac_Float_Bench_32-25th,88.000,64.800,106.800
+culturalsolutions.co.uk.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,223.590,180.052,255.949
+culturalsolutions.co.uk.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,55.140,36.869,70.654
+culturalsolutions.co.uk.skp_tile_256x1024_,Mac_Float_Bench_32-25th,102.000,76.700,122.200
+culturalsolutions.co.uk.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,227.780,183.613,260.558
+culturalsolutions.co.uk.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,58.560,39.776,74.416
+culturalsolutions.co.uk.skp_tile_256x256_,Mac_Float_Bench_32-25th,97.000,72.450,116.700
+culturalsolutions.co.uk.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,234.420,189.257,267.862
+culturalsolutions.co.uk.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,58.940,40.099,74.834
+culturalsolutions.co.uk.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,81.000,58.850,99.100
+culturalsolutions.co.uk.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,400.630,330.536,450.693
+culturalsolutions.co.uk.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,104.810,79.088,125.291
+culturalsolutions.co.uk.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,103.870,78.290,124.257
+culturalsolutions.co.uk.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,88.000,64.800,106.800
+culturalsolutions.co.uk.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,194.560,155.376,224.016
+culturalsolutions.co.uk.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,53.140,35.169,68.454
+culturalsolutions.co.uk.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,62.000,42.700,78.200
+culturalsolutions.co.uk.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,198.430,158.666,228.273
+culturalsolutions.co.uk.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,43.550,27.017,57.905
+culturalsolutions.co.uk.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,60.000,41.000,76.000
+culturalsolutions.co.uk.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,168.450,133.182,195.295
+culturalsolutions.co.uk.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,40.650,24.552,54.715
+culturalsolutions.co.uk.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,57.000,38.450,72.700
+culturalsolutions.co.uk.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,167.520,132.392,194.272
+culturalsolutions.co.uk.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,39.130,23.261,53.043
+culturalsolutions.co.uk.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,88.000,64.800,106.800
+culturalsolutions.co.uk.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,198.900,159.065,228.790
+culturalsolutions.co.uk.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,54.360,36.206,69.796
+culturalsolutions.co.uk.skp_tile_512x512_,Mac_Float_Bench_32-25th,91.000,67.350,110.100
+culturalsolutions.co.uk.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,205.140,164.369,235.654
+culturalsolutions.co.uk.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,52.120,34.302,67.332
+culturalsolutions.co.uk.skp_tile_64x1024_,Mac_Float_Bench_32-25th,148.000,115.800,172.800
+culturalsolutions.co.uk.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,319.060,261.201,360.966
+culturalsolutions.co.uk.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,93.380,69.373,112.718
+cuteoverload.skp_copy_tiles_,Mac_Float_Bench_32-25th,143.000,111.550,167.300
+cuteoverload.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,140.640,109.544,164.704
+cuteoverload.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,35.880,20.498,49.468
+cuteoverload.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+cuteoverload.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.270,-9.771,10.296
+cuteoverload.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.056,-9.952,10.062
+cuteoverload.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+cuteoverload.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,0.906,-9.230,10.996
+cuteoverload.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.294,-9.750,10.324
+cuteoverload.skp_record_,Mac_Float_Bench_32-25th,59.000,40.150,74.900
+cuteoverload.skp_record_,Nexus10_4-1_Float_Bench_32-25th,58.400,39.640,74.240
+cuteoverload.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,16.308,3.862,27.939
+cuteoverload.skp_record_grid_,Mac_Float_Bench_32-25th,61.000,41.850,77.100
+cuteoverload.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,58.403,39.642,74.243
+cuteoverload.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,16.829,4.305,28.512
+cuteoverload.skp_record_rtree_,Mac_Float_Bench_32-25th,61.000,41.850,77.100
+cuteoverload.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,60.728,41.619,76.801
+cuteoverload.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,16.775,4.259,28.453
+cuteoverload.skp_tile_1024x256_,Mac_Float_Bench_32-25th,64.000,44.400,80.400
+cuteoverload.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,145.490,113.666,170.039
+cuteoverload.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,48.090,30.877,62.899
+cuteoverload.skp_tile_1024x64_,Mac_Float_Bench_32-25th,129.000,99.650,151.900
+cuteoverload.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,251.300,203.605,286.430
+cuteoverload.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,81.050,58.892,99.155
+cuteoverload.skp_tile_256x1024_,Mac_Float_Bench_32-25th,68.000,47.800,84.800
+cuteoverload.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,188.650,150.352,217.515
+cuteoverload.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,48.420,31.157,63.262
+cuteoverload.skp_tile_256x256_,Mac_Float_Bench_32-25th,104.000,78.400,124.400
+cuteoverload.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,217.220,174.637,248.942
+cuteoverload.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,66.490,46.516,83.139
+cuteoverload.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,206.000,165.100,236.600
+cuteoverload.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,935.160,784.886,1038.676
+cuteoverload.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,191.090,152.427,220.199
+cuteoverload.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,190.340,151.789,219.374
+cuteoverload.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,48.000,30.800,62.800
+cuteoverload.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,83.220,60.737,101.542
+cuteoverload.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,42.160,25.836,56.376
+cuteoverload.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,61.000,41.850,77.100
+cuteoverload.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,187.840,149.664,216.624
+cuteoverload.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,46.020,29.117,60.622
+cuteoverload.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,65.000,45.250,81.500
+cuteoverload.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,216.200,173.770,247.820
+cuteoverload.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,38.720,22.912,52.592
+cuteoverload.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,64.000,44.400,80.400
+cuteoverload.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,206.680,165.678,237.348
+cuteoverload.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,37.140,21.569,50.854
+cuteoverload.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,49.000,31.650,63.900
+cuteoverload.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,87.230,64.145,105.953
+cuteoverload.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,42.580,26.193,56.838
+cuteoverload.skp_tile_512x512_,Mac_Float_Bench_32-25th,63.000,43.550,79.300
+cuteoverload.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,157.990,124.292,183.789
+cuteoverload.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,48.010,30.808,62.811
+cuteoverload.skp_tile_64x1024_,Mac_Float_Bench_32-25th,126.000,97.100,148.600
+cuteoverload.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,256.080,207.668,291.688
+cuteoverload.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,83.140,60.669,101.454
+dashed-spreadsheet.skp_copy_tiles_,Mac_Float_Bench_32-25th,52.000,34.200,67.200
+dashed-spreadsheet.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,150.570,117.984,175.627
+dashed-spreadsheet.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,16.770,4.254,28.447
+dashed-spreadsheet.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+dashed-spreadsheet.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.028,-9.976,10.031
+dashed-spreadsheet.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,5.000,-5.750,15.500
+dashed-spreadsheet.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,2.402,-7.958,12.642
+dashed-spreadsheet.skp_record_,Mac_Float_Bench_32-25th,12.000,0.200,23.200
+dashed-spreadsheet.skp_record_,Nexus10_4-1_Float_Bench_32-25th,22.808,9.387,35.089
+dashed-spreadsheet.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,5.256,-5.532,15.782
+dashed-spreadsheet.skp_record_grid_,Mac_Float_Bench_32-25th,14.000,1.900,25.400
+dashed-spreadsheet.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,27.851,13.673,40.636
+dashed-spreadsheet.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,6.450,-4.518,17.095
+dashed-spreadsheet.skp_record_rtree_,Mac_Float_Bench_32-25th,19.000,6.150,30.900
+dashed-spreadsheet.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,34.214,19.082,47.636
+dashed-spreadsheet.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,8.937,-2.404,19.830
+dashed-spreadsheet.skp_tile_1024x256_,Mac_Float_Bench_32-25th,56.000,37.600,71.600
+dashed-spreadsheet.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,186.950,148.907,215.645
+dashed-spreadsheet.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,24.680,10.978,37.148
+dashed-spreadsheet.skp_tile_1024x64_,Mac_Float_Bench_32-25th,120.000,92.000,142.000
+dashed-spreadsheet.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,321.380,263.173,363.518
+dashed-spreadsheet.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,53.540,35.509,68.894
+dashed-spreadsheet.skp_tile_256x256_,Mac_Float_Bench_32-25th,94.000,69.900,113.400
+dashed-spreadsheet.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,310.210,253.678,351.231
+dashed-spreadsheet.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,40.010,24.008,54.011
+dashed-spreadsheet.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,416.000,343.600,467.600
+dashed-spreadsheet.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,1899.460,1604.541,2099.406
+dashed-spreadsheet.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,266.710,216.703,303.381
+dashed-spreadsheet.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,264.800,215.080,301.280
+dashed-spreadsheet.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,32.000,17.200,45.200
+dashed-spreadsheet.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,151.670,118.919,176.837
+dashed-spreadsheet.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,16.100,3.685,27.710
+dashed-spreadsheet.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,49.000,31.650,63.900
+dashed-spreadsheet.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,244.940,198.199,279.434
+dashed-spreadsheet.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,22.890,9.456,35.179
+dashed-spreadsheet.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,51.000,33.350,66.100
+dashed-spreadsheet.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,252.640,204.744,287.904
+dashed-spreadsheet.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,24.870,11.140,37.357
+dashed-spreadsheet.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,50.000,32.500,65.000
+dashed-spreadsheet.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,256.850,208.323,292.535
+dashed-spreadsheet.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,19.150,6.277,31.065
+dashed-spreadsheet.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,33.000,18.050,46.300
+dashed-spreadsheet.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,152.200,119.370,177.420
+dashed-spreadsheet.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,16.530,4.051,28.183
+dashed-spreadsheet.skp_tile_512x512_,Mac_Float_Bench_32-25th,50.000,32.500,65.000
+dashed-spreadsheet.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,177.470,140.850,205.217
+dashed-spreadsheet.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,22.820,9.397,35.102
+digg.skp_copy_tiles_,Mac_Float_Bench_32-25th,87.000,63.950,105.700
+digg.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,135.450,105.132,158.995
+digg.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,20.590,7.502,32.649
+digg.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+digg.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.168,-9.857,10.185
+digg.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.036,-9.969,10.040
+digg.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,1.000,-9.150,11.100
+digg.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,1.376,-8.831,11.513
+digg.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.482,-9.590,10.530
+digg.skp_record_,Mac_Float_Bench_32-25th,12.000,0.200,23.200
+digg.skp_record_,Nexus10_4-1_Float_Bench_32-25th,7.281,-3.811,18.010
+digg.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,2.226,-8.108,12.449
+digg.skp_record_grid_,Mac_Float_Bench_32-25th,14.000,1.900,25.400
+digg.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,10.440,-1.126,21.484
+digg.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,2.687,-7.716,12.956
+digg.skp_record_rtree_,Mac_Float_Bench_32-25th,15.000,2.750,26.500
+digg.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,11.165,-0.510,22.282
+digg.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.126,-7.343,13.439
+digg.skp_tile_1024x256_,Mac_Float_Bench_32-25th,47.000,29.950,61.700
+digg.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,121.470,93.249,143.617
+digg.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,23.010,9.559,35.311
+digg.skp_tile_1024x64_,Mac_Float_Bench_32-25th,63.000,43.550,79.300
+digg.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,134.110,103.994,157.521
+digg.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,33.290,18.296,46.619
+digg.skp_tile_256x1024_,Mac_Float_Bench_32-25th,54.000,35.900,69.400
+digg.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,145.940,114.049,170.534
+digg.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,25.740,11.879,38.314
+digg.skp_tile_256x256_,Mac_Float_Bench_32-25th,63.000,43.550,79.300
+digg.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,144.420,112.757,168.862
+digg.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,32.780,17.863,46.058
+digg.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,105.000,79.250,125.500
+digg.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,467.870,387.690,524.657
+digg.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,83.660,61.111,102.026
+digg.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,82.930,60.490,101.223
+digg.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,43.000,26.550,57.300
+digg.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,83.130,60.660,101.443
+digg.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,21.500,8.275,33.650
+digg.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,39.000,23.150,52.900
+digg.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,102.920,77.482,123.212
+digg.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,29.420,15.007,42.362
+digg.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,37.000,21.450,50.700
+digg.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,97.060,72.501,116.766
+digg.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,18.870,6.040,30.757
+digg.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,37.000,21.450,50.700
+digg.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,131.480,101.758,154.628
+digg.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,21.570,8.334,33.727
+digg.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,44.000,27.400,58.400
+digg.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,92.780,68.863,112.058
+digg.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,22.190,8.861,34.409
+digg.skp_tile_512x512_,Mac_Float_Bench_32-25th,50.000,32.500,65.000
+digg.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,135.800,105.430,159.380
+digg.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,23.740,10.179,36.114
+digg.skp_tile_64x1024_,Mac_Float_Bench_32-25th,103.000,77.550,123.300
+digg.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,226.040,182.134,258.644
+digg.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,51.380,33.673,66.518
+frantzen-lindeberg.skp_copy_tiles_,Mac_Float_Bench_32-25th,191.000,152.350,220.100
+frantzen-lindeberg.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,228.590,184.302,261.449
+frantzen-lindeberg.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,41.790,25.521,55.969
+frantzen-lindeberg.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+frantzen-lindeberg.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.021,-9.983,10.023
+frantzen-lindeberg.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+frantzen-lindeberg.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.046,-9.961,10.051
+frantzen-lindeberg.skp_record_,Mac_Float_Bench_32-25th,53.000,35.050,68.300
+frantzen-lindeberg.skp_record_,Nexus10_4-1_Float_Bench_32-25th,37.175,21.599,50.892
+frantzen-lindeberg.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,10.364,-1.190,21.401
+frantzen-lindeberg.skp_record_grid_,Mac_Float_Bench_32-25th,53.000,35.050,68.300
+frantzen-lindeberg.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,47.134,30.064,61.847
+frantzen-lindeberg.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,12.676,0.775,23.944
+frantzen-lindeberg.skp_record_rtree_,Mac_Float_Bench_32-25th,53.000,35.050,68.300
+frantzen-lindeberg.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,41.331,25.131,55.464
+frantzen-lindeberg.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,12.524,0.645,23.776
+frantzen-lindeberg.skp_tile_1024x256_,Mac_Float_Bench_32-25th,98.000,73.300,117.800
+frantzen-lindeberg.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,223.970,180.374,256.367
+frantzen-lindeberg.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,39.590,23.651,53.549
+frantzen-lindeberg.skp_tile_1024x64_,Mac_Float_Bench_32-25th,102.000,76.700,122.200
+frantzen-lindeberg.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,220.920,177.782,253.012
+frantzen-lindeberg.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,43.230,26.745,57.553
+frantzen-lindeberg.skp_tile_256x256_,Mac_Float_Bench_32-25th,106.000,80.100,126.600
+frantzen-lindeberg.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,241.830,195.555,276.013
+frantzen-lindeberg.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,44.500,27.825,58.950
+frantzen-lindeberg.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,84.000,61.400,102.400
+frantzen-lindeberg.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,252.970,205.024,288.267
+frantzen-lindeberg.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,24.080,10.468,36.488
+frantzen-lindeberg.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,22.970,9.524,35.267
+frantzen-lindeberg.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,102.000,76.700,122.200
+frantzen-lindeberg.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,240.280,194.238,274.308
+frantzen-lindeberg.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,42.890,26.456,57.179
+frantzen-lindeberg.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,72.000,51.200,89.200
+frantzen-lindeberg.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,186.040,148.134,214.644
+frantzen-lindeberg.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,29.940,15.449,42.934
+frantzen-lindeberg.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,60.000,41.000,76.000
+frantzen-lindeberg.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,178.620,141.827,206.482
+frantzen-lindeberg.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,22.560,9.176,34.816
+frantzen-lindeberg.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,60.000,41.000,76.000
+frantzen-lindeberg.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,186.730,148.720,215.403
+frantzen-lindeberg.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,17.450,4.832,29.195
+frantzen-lindeberg.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,101.000,75.850,121.100
+frantzen-lindeberg.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,220.680,177.578,252.748
+frantzen-lindeberg.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,41.810,25.538,55.991
+frantzen-lindeberg.skp_tile_512x512_,Mac_Float_Bench_32-25th,106.000,80.100,126.600
+frantzen-lindeberg.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,234.290,189.146,267.719
+frantzen-lindeberg.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,42.450,26.083,56.695
+game.deksiam.in.th.skp_copy_tiles_,Mac_Float_Bench_32-25th,57.000,38.450,72.700
+game.deksiam.in.th.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,65.770,45.904,82.347
+game.deksiam.in.th.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,14.420,2.257,25.862
+game.deksiam.in.th.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+game.deksiam.in.th.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.208,-9.823,10.229
+game.deksiam.in.th.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.034,-9.971,10.038
+game.deksiam.in.th.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,1.000,-9.150,11.100
+game.deksiam.in.th.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,2.279,-8.063,12.507
+game.deksiam.in.th.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.800,-9.320,10.880
+game.deksiam.in.th.skp_record_,Mac_Float_Bench_32-25th,6.000,-4.900,16.600
+game.deksiam.in.th.skp_record_,Nexus10_4-1_Float_Bench_32-25th,7.624,-3.520,18.386
+game.deksiam.in.th.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,1.935,-8.355,12.129
+game.deksiam.in.th.skp_record_grid_,Mac_Float_Bench_32-25th,7.000,-4.050,17.700
+game.deksiam.in.th.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,10.636,-0.960,21.699
+game.deksiam.in.th.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,2.630,-7.764,12.893
+game.deksiam.in.th.skp_record_rtree_,Mac_Float_Bench_32-25th,8.000,-3.200,18.800
+game.deksiam.in.th.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,12.203,0.373,23.424
+game.deksiam.in.th.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.326,-7.173,13.659
+game.deksiam.in.th.skp_tile_1024x256_,Mac_Float_Bench_32-25th,33.000,18.050,46.300
+game.deksiam.in.th.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,66.110,46.194,82.721
+game.deksiam.in.th.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,16.500,4.025,28.150
+game.deksiam.in.th.skp_tile_1024x64_,Mac_Float_Bench_32-25th,47.000,29.950,61.700
+game.deksiam.in.th.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,87.710,64.553,106.481
+game.deksiam.in.th.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,24.880,11.148,37.368
+game.deksiam.in.th.skp_tile_256x1024_,Mac_Float_Bench_32-25th,31.000,16.350,44.100
+game.deksiam.in.th.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,65.570,45.734,82.127
+game.deksiam.in.th.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,15.340,3.039,26.874
+game.deksiam.in.th.skp_tile_256x256_,Mac_Float_Bench_32-25th,37.000,21.450,50.700
+game.deksiam.in.th.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,66.940,46.899,83.634
+game.deksiam.in.th.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,19.740,6.779,31.714
+game.deksiam.in.th.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,138.000,107.300,161.800
+game.deksiam.in.th.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,977.920,821.232,1085.712
+game.deksiam.in.th.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,115.210,87.928,136.731
+game.deksiam.in.th.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,114.000,86.900,135.400
+game.deksiam.in.th.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,30.000,15.500,43.000
+game.deksiam.in.th.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,52.880,34.948,68.168
+game.deksiam.in.th.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,15.910,3.524,27.501
+game.deksiam.in.th.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,28.000,13.800,40.800
+game.deksiam.in.th.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,58.270,39.529,74.097
+game.deksiam.in.th.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,21.150,7.977,33.265
+game.deksiam.in.th.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,24.000,10.400,36.400
+game.deksiam.in.th.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,50.880,33.248,65.968
+game.deksiam.in.th.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,16.930,4.390,28.623
+game.deksiam.in.th.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,23.000,9.550,35.300
+game.deksiam.in.th.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,60.660,41.561,76.726
+game.deksiam.in.th.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,14.750,2.537,26.225
+game.deksiam.in.th.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,31.000,16.350,44.100
+game.deksiam.in.th.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,50.470,32.899,65.517
+game.deksiam.in.th.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,15.700,3.345,27.270
+game.deksiam.in.th.skp_tile_512x512_,Mac_Float_Bench_32-25th,32.000,17.200,45.200
+game.deksiam.in.th.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,65.700,45.845,82.270
+game.deksiam.in.th.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,16.530,4.051,28.183
+game.deksiam.in.th.skp_tile_64x1024_,Mac_Float_Bench_32-25th,54.000,35.900,69.400
+game.deksiam.in.th.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,100.420,75.357,120.462
+game.deksiam.in.th.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,28.970,14.624,41.867
+gmail-thread.skp_copy_tiles_,Mac_Float_Bench_32-25th,19.000,6.150,30.900
+gmail-thread.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,25.820,11.947,38.402
+gmail-thread.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,5.110,-5.656,15.621
+gmail-thread.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+gmail-thread.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.158,-9.866,10.174
+gmail-thread.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.026,-9.978,10.028
+gmail-thread.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+gmail-thread.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,0.417,-9.645,10.459
+gmail-thread.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.124,-9.894,10.137
+gmail-thread.skp_record_,Mac_Float_Bench_32-25th,1.000,-9.150,11.100
+gmail-thread.skp_record_,Nexus10_4-1_Float_Bench_32-25th,2.465,-7.905,12.712
+gmail-thread.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.538,-9.542,10.592
+gmail-thread.skp_record_grid_,Mac_Float_Bench_32-25th,1.000,-9.150,11.100
+gmail-thread.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,3.257,-7.231,13.583
+gmail-thread.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.693,-9.411,10.762
+gmail-thread.skp_record_rtree_,Mac_Float_Bench_32-25th,1.000,-9.150,11.100
+gmail-thread.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,3.383,-7.125,13.721
+gmail-thread.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.780,-9.337,10.858
+gmail-thread.skp_tile_1024x256_,Mac_Float_Bench_32-25th,11.000,-0.650,22.100
+gmail-thread.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,21.880,8.598,34.068
+gmail-thread.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,5.850,-5.028,16.435
+gmail-thread.skp_tile_1024x64_,Mac_Float_Bench_32-25th,17.000,4.450,28.700
+gmail-thread.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,30.330,15.780,43.363
+gmail-thread.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,8.690,-2.614,19.559
+gmail-thread.skp_tile_256x1024_,Mac_Float_Bench_32-25th,11.000,-0.650,22.100
+gmail-thread.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,27.900,13.715,40.690
+gmail-thread.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,6.520,-4.458,17.172
+gmail-thread.skp_tile_256x256_,Mac_Float_Bench_32-25th,15.000,2.750,26.500
+gmail-thread.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,28.480,14.208,41.328
+gmail-thread.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,7.550,-3.583,18.305
+gmail-thread.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,48.000,30.800,62.800
+gmail-thread.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,168.320,133.072,195.152
+gmail-thread.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,28.890,14.556,41.779
+gmail-thread.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,28.000,13.800,40.800
+gmail-thread.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,10.000,-1.500,21.000
+gmail-thread.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,15.930,3.540,27.523
+gmail-thread.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,5.560,-5.274,16.116
+gmail-thread.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,9.000,-2.350,19.900
+gmail-thread.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,22.250,8.912,34.475
+gmail-thread.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,8.970,-2.375,19.867
+gmail-thread.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,8.000,-3.200,18.800
+gmail-thread.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,24.560,10.876,37.016
+gmail-thread.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,7.870,-3.311,18.657
+gmail-thread.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,8.000,-3.200,18.800
+gmail-thread.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,20.110,7.093,32.121
+gmail-thread.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,5.780,-5.087,16.358
+gmail-thread.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,10.000,-1.500,21.000
+gmail-thread.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,17.120,4.552,28.832
+gmail-thread.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,5.580,-5.257,16.138
+gmail-thread.skp_tile_512x512_,Mac_Float_Bench_32-25th,11.000,-0.650,22.100
+gmail-thread.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,22.060,8.751,34.266
+gmail-thread.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,5.730,-5.130,16.303
+gmail-thread.skp_tile_64x1024_,Mac_Float_Bench_32-25th,20.000,7.000,32.000
+gmail-thread.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,36.660,21.161,50.326
+gmail-thread.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,10.140,-1.381,21.154
+gmail.skp_copy_tiles_,Mac_Float_Bench_32-25th,23.000,9.550,35.300
+gmail.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,36.540,21.059,50.194
+gmail.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,7.150,-3.922,17.865
+gmail.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+gmail.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.168,-9.858,10.184
+gmail.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.026,-9.978,10.029
+gmail.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,1.000,-9.150,11.100
+gmail.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,1.671,-8.580,11.838
+gmail.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.569,-9.516,10.626
+gmail.skp_record_,Mac_Float_Bench_32-25th,2.000,-8.300,12.200
+gmail.skp_record_,Nexus10_4-1_Float_Bench_32-25th,4.681,-6.021,15.149
+gmail.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,1.040,-9.116,11.144
+gmail.skp_record_grid_,Mac_Float_Bench_32-25th,3.000,-7.450,13.300
+gmail.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,6.575,-4.411,17.233
+gmail.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,1.494,-8.730,11.644
+gmail.skp_record_rtree_,Mac_Float_Bench_32-25th,4.000,-6.600,14.400
+gmail.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,8.280,-2.962,19.108
+gmail.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,2.019,-8.284,12.220
+gmail.skp_tile_1024x256_,Mac_Float_Bench_32-25th,17.000,4.450,28.700
+gmail.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,37.440,21.824,51.184
+gmail.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,9.060,-2.299,19.966
+gmail.skp_tile_1024x64_,Mac_Float_Bench_32-25th,28.000,13.800,40.800
+gmail.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,51.870,34.089,67.057
+gmail.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,15.410,3.098,26.951
+gmail.skp_tile_256x1024_,Mac_Float_Bench_32-25th,18.000,5.300,29.800
+gmail.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,43.580,27.043,57.938
+gmail.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,9.620,-1.823,20.582
+gmail.skp_tile_256x256_,Mac_Float_Bench_32-25th,24.000,10.400,36.400
+gmail.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,53.910,35.823,69.301
+gmail.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,13.310,1.313,24.641
+gmail.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,68.000,47.800,84.800
+gmail.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,260.850,211.723,296.935
+gmail.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,46.710,29.703,61.381
+gmail.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,45.860,28.981,60.446
+gmail.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,15.000,2.750,26.500
+gmail.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,28.020,13.817,40.822
+gmail.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,7.810,-3.362,18.591
+gmail.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,14.000,1.900,25.400
+gmail.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,36.010,20.608,49.611
+gmail.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,7.670,-3.481,18.437
+gmail.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,15.000,2.750,26.500
+gmail.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,28.660,14.361,41.526
+gmail.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,5.610,-5.231,16.171
+gmail.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,13.000,1.050,24.300
+gmail.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,31.270,16.579,44.397
+gmail.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,5.940,-4.951,16.534
+gmail.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,16.000,3.600,27.600
+gmail.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,26.160,12.236,38.776
+gmail.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,8.100,-3.115,18.910
+gmail.skp_tile_512x512_,Mac_Float_Bench_32-25th,17.000,4.450,28.700
+gmail.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,37.140,21.569,50.854
+gmail.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,8.960,-2.384,19.856
+gmail.skp_tile_64x1024_,Mac_Float_Bench_32-25th,32.000,17.200,45.200
+gmail.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,62.450,43.083,78.695
+gmail.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,17.060,4.501,28.766
+googleblog.blogspot.co.uk.skp_copy_tiles_,Mac_Float_Bench_32-25th,150.000,117.500,175.000
+googleblog.blogspot.co.uk.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,181.360,144.156,209.496
+googleblog.blogspot.co.uk.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,32.820,17.897,46.102
+googleblog.blogspot.co.uk.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+googleblog.blogspot.co.uk.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.298,-9.747,10.328
+googleblog.blogspot.co.uk.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.061,-9.948,10.067
+googleblog.blogspot.co.uk.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,1.000,-9.150,11.100
+googleblog.blogspot.co.uk.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,1.480,-8.742,11.628
+googleblog.blogspot.co.uk.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.516,-9.561,10.568
+googleblog.blogspot.co.uk.skp_record_,Mac_Float_Bench_32-25th,18.000,5.300,29.800
+googleblog.blogspot.co.uk.skp_record_,Nexus10_4-1_Float_Bench_32-25th,20.461,7.392,32.507
+googleblog.blogspot.co.uk.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.399,-7.111,13.739
+googleblog.blogspot.co.uk.skp_record_grid_,Mac_Float_Bench_32-25th,20.000,7.000,32.000
+googleblog.blogspot.co.uk.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,22.654,9.256,34.920
+googleblog.blogspot.co.uk.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,4.462,-6.207,14.909
+googleblog.blogspot.co.uk.skp_record_rtree_,Mac_Float_Bench_32-25th,20.000,7.000,32.000
+googleblog.blogspot.co.uk.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,23.206,9.725,35.526
+googleblog.blogspot.co.uk.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,4.516,-6.162,14.967
+googleblog.blogspot.co.uk.skp_tile_1024x256_,Mac_Float_Bench_32-25th,75.000,53.750,92.500
+googleblog.blogspot.co.uk.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,160.440,126.374,186.484
+googleblog.blogspot.co.uk.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,37.110,21.543,50.821
+googleblog.blogspot.co.uk.skp_tile_1024x64_,Mac_Float_Bench_32-25th,109.000,82.650,129.900
+googleblog.blogspot.co.uk.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,203.380,162.873,233.718
+googleblog.blogspot.co.uk.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,53.350,35.347,68.685
+googleblog.blogspot.co.uk.skp_tile_256x1024_,Mac_Float_Bench_32-25th,84.000,61.400,102.400
+googleblog.blogspot.co.uk.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,192.470,153.600,221.717
+googleblog.blogspot.co.uk.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,40.840,24.714,54.924
+googleblog.blogspot.co.uk.skp_tile_256x256_,Mac_Float_Bench_32-25th,108.000,81.800,128.800
+googleblog.blogspot.co.uk.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,194.690,155.487,224.159
+googleblog.blogspot.co.uk.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,54.280,36.138,69.708
+googleblog.blogspot.co.uk.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,214.000,171.900,245.400
+googleblog.blogspot.co.uk.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,914.710,767.504,1016.181
+googleblog.blogspot.co.uk.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,157.520,123.892,183.272
+googleblog.blogspot.co.uk.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,156.980,123.433,182.678
+googleblog.blogspot.co.uk.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,75.000,53.750,92.500
+googleblog.blogspot.co.uk.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,118.810,90.989,140.691
+googleblog.blogspot.co.uk.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,38.200,22.470,52.020
+googleblog.blogspot.co.uk.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,57.000,38.450,72.700
+googleblog.blogspot.co.uk.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,150.040,117.534,175.044
+googleblog.blogspot.co.uk.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,33.440,18.424,46.784
+googleblog.blogspot.co.uk.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,57.000,38.450,72.700
+googleblog.blogspot.co.uk.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,167.970,132.774,194.767
+googleblog.blogspot.co.uk.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,24.610,10.918,37.071
+googleblog.blogspot.co.uk.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,58.000,39.300,73.800
+googleblog.blogspot.co.uk.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,153.620,120.577,178.982
+googleblog.blogspot.co.uk.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,26.690,12.686,39.359
+googleblog.blogspot.co.uk.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,75.000,53.750,92.500
+googleblog.blogspot.co.uk.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,120.990,92.841,143.089
+googleblog.blogspot.co.uk.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,38.700,22.895,52.570
+googleblog.blogspot.co.uk.skp_tile_512x512_,Mac_Float_Bench_32-25th,81.000,58.850,99.100
+googleblog.blogspot.co.uk.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,177.970,141.274,205.767
+googleblog.blogspot.co.uk.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,39.660,23.711,53.626
+googleblog.blogspot.co.uk.skp_tile_64x1024_,Mac_Float_Bench_32-25th,143.000,111.550,167.300
+googleblog.blogspot.co.uk.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,260.040,211.034,296.044
+googleblog.blogspot.co.uk.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,70.740,50.129,87.814
+gsp.ro.skp_copy_tiles_,Mac_Float_Bench_32-25th,261.000,211.850,297.100
+gsp.ro.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,369.630,304.185,416.593
+gsp.ro.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,164.970,130.225,191.467
+gsp.ro.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+gsp.ro.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,1.259,-8.930,11.385
+gsp.ro.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.252,-9.786,10.277
+gsp.ro.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,2.000,-8.300,12.200
+gsp.ro.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,3.995,-6.604,14.395
+gsp.ro.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,1.205,-8.976,11.325
+gsp.ro.skp_record_,Mac_Float_Bench_32-25th,30.000,15.500,43.000
+gsp.ro.skp_record_,Nexus10_4-1_Float_Bench_32-25th,36.040,20.634,49.644
+gsp.ro.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,6.760,-4.254,17.436
+gsp.ro.skp_record_grid_,Mac_Float_Bench_32-25th,34.000,18.900,47.400
+gsp.ro.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,47.731,30.571,62.504
+gsp.ro.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,8.594,-2.695,19.454
+gsp.ro.skp_record_rtree_,Mac_Float_Bench_32-25th,35.000,19.750,48.500
+gsp.ro.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,49.328,31.929,64.260
+gsp.ro.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,9.569,-1.866,20.526
+gsp.ro.skp_tile_1024x256_,Mac_Float_Bench_32-25th,160.000,126.000,186.000
+gsp.ro.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,383.550,316.017,431.905
+gsp.ro.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,144.710,113.004,169.181
+gsp.ro.skp_tile_1024x64_,Mac_Float_Bench_32-25th,236.000,190.600,269.600
+gsp.ro.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,519.210,431.329,581.131
+gsp.ro.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,195.950,156.557,225.545
+gsp.ro.skp_tile_256x1024_,Mac_Float_Bench_32-25th,211.000,169.350,242.100
+gsp.ro.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,508.760,422.446,569.636
+gsp.ro.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,158.950,125.107,184.845
+gsp.ro.skp_tile_256x256_,Mac_Float_Bench_32-25th,210.000,168.500,241.000
+gsp.ro.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,502.390,417.031,562.629
+gsp.ro.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,181.640,144.394,209.804
+gsp.ro.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,344.000,282.400,388.400
+gsp.ro.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,1592.890,1343.957,1762.179
+gsp.ro.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,445.790,368.921,500.369
+gsp.ro.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,443.950,367.357,498.345
+gsp.ro.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,131.000,101.350,154.100
+gsp.ro.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,253.080,205.118,288.388
+gsp.ro.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,134.720,104.512,158.192
+gsp.ro.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,140.000,109.000,164.000
+gsp.ro.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,442.790,366.372,497.069
+gsp.ro.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,220.910,177.773,253.001
+gsp.ro.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,134.000,103.900,157.400
+gsp.ro.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,458.090,379.376,513.899
+gsp.ro.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,203.970,163.374,234.367
+gsp.ro.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,134.000,103.900,157.400
+gsp.ro.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,479.920,397.932,537.912
+gsp.ro.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,212.340,170.489,243.574
+gsp.ro.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,131.000,101.350,154.100
+gsp.ro.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,254.910,206.673,290.401
+gsp.ro.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,135.930,105.541,159.523
+gsp.ro.skp_tile_512x512_,Mac_Float_Bench_32-25th,162.000,127.700,188.200
+gsp.ro.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,381.780,314.513,429.958
+gsp.ro.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,146.280,114.338,170.908
+gsp.ro.skp_tile_64x1024_,Mac_Float_Bench_32-25th,343.000,281.550,387.300
+gsp.ro.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,792.570,663.685,881.827
+gsp.ro.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,236.100,190.685,269.710
+hs.fi.skp_copy_tiles_,Mac_Float_Bench_32-25th,128.000,98.800,150.800
+hs.fi.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,141.280,110.088,165.408
+hs.fi.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,62.480,43.108,78.728
+hs.fi.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+hs.fi.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.248,-9.789,10.272
+hs.fi.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.084,-9.929,10.092
+hs.fi.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,1.000,-9.150,11.100
+hs.fi.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,1.343,-8.859,11.477
+hs.fi.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.507,-9.569,10.557
+hs.fi.skp_record_,Mac_Float_Bench_32-25th,22.000,8.700,34.200
+hs.fi.skp_record_,Nexus10_4-1_Float_Bench_32-25th,21.437,8.221,33.580
+hs.fi.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,5.220,-5.563,15.742
+hs.fi.skp_record_grid_,Mac_Float_Bench_32-25th,23.000,9.550,35.300
+hs.fi.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,24.913,11.176,37.404
+hs.fi.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,5.842,-5.034,16.426
+hs.fi.skp_record_rtree_,Mac_Float_Bench_32-25th,24.000,10.400,36.400
+hs.fi.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,25.107,11.341,37.618
+hs.fi.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,6.079,-4.833,16.687
+hs.fi.skp_tile_1024x256_,Mac_Float_Bench_32-25th,58.000,39.300,73.800
+hs.fi.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,138.460,107.691,162.306
+hs.fi.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,57.660,39.011,73.426
+hs.fi.skp_tile_1024x64_,Mac_Float_Bench_32-25th,85.000,62.250,103.500
+hs.fi.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,166.570,131.584,193.227
+hs.fi.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,73.980,52.883,91.378
+hs.fi.skp_tile_256x1024_,Mac_Float_Bench_32-25th,69.000,48.650,85.900
+hs.fi.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,162.190,127.862,188.409
+hs.fi.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,64.200,44.570,80.620
+hs.fi.skp_tile_256x256_,Mac_Float_Bench_32-25th,79.000,57.150,96.900
+hs.fi.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,165.390,130.581,191.929
+hs.fi.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,70.730,50.120,87.803
+hs.fi.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,131.000,101.350,154.100
+hs.fi.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,564.110,469.493,630.521
+hs.fi.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,151.530,118.800,176.683
+hs.fi.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,150.070,117.559,175.077
+hs.fi.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,52.000,34.200,67.200
+hs.fi.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,91.760,67.996,110.936
+hs.fi.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,53.930,35.840,69.323
+hs.fi.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,46.000,29.100,60.600
+hs.fi.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,122.160,93.836,144.376
+hs.fi.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,58.450,39.683,74.295
+hs.fi.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,47.000,29.950,61.700
+hs.fi.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,146.320,114.372,170.952
+hs.fi.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,55.240,36.954,70.764
+hs.fi.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,46.000,29.100,60.600
+hs.fi.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,134.350,104.197,157.785
+hs.fi.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,60.630,41.535,76.693
+hs.fi.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,53.000,35.050,68.300
+hs.fi.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,92.710,68.803,111.981
+hs.fi.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,54.350,36.197,69.785
+hs.fi.skp_tile_512x512_,Mac_Float_Bench_32-25th,60.000,41.000,76.000
+hs.fi.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,142.360,111.006,166.596
+hs.fi.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,59.380,40.473,75.318
+hs.fi.skp_tile_64x1024_,Mac_Float_Bench_32-25th,125.000,96.250,147.500
+hs.fi.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,250.700,203.095,285.770
+hs.fi.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,98.130,73.410,117.943
+iphone.capitolvolkswagen.skp_copy_tiles_,Mac_Float_Bench_32-25th,100.000,75.000,120.000
+iphone.capitolvolkswagen.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,81.290,59.097,99.419
+iphone.capitolvolkswagen.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,14.140,2.019,25.554
+iphone.capitolvolkswagen.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+iphone.capitolvolkswagen.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.115,-9.903,10.126
+iphone.capitolvolkswagen.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.019,-9.984,10.021
+iphone.capitolvolkswagen.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+iphone.capitolvolkswagen.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,0.244,-9.793,10.268
+iphone.capitolvolkswagen.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.066,-9.944,10.073
+iphone.capitolvolkswagen.skp_record_,Mac_Float_Bench_32-25th,1.000,-9.150,11.100
+iphone.capitolvolkswagen.skp_record_,Nexus10_4-1_Float_Bench_32-25th,2.530,-7.850,12.783
+iphone.capitolvolkswagen.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.536,-9.545,10.589
+iphone.capitolvolkswagen.skp_record_grid_,Mac_Float_Bench_32-25th,1.000,-9.150,11.100
+iphone.capitolvolkswagen.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,3.764,-6.800,14.141
+iphone.capitolvolkswagen.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.780,-9.337,10.858
+iphone.capitolvolkswagen.skp_record_rtree_,Mac_Float_Bench_32-25th,1.000,-9.150,11.100
+iphone.capitolvolkswagen.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,3.520,-7.008,13.872
+iphone.capitolvolkswagen.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.807,-9.314,10.887
+iphone.capitolvolkswagen.skp_tile_1024x256_,Mac_Float_Bench_32-25th,28.000,13.800,40.800
+iphone.capitolvolkswagen.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,51.060,33.401,66.166
+iphone.capitolvolkswagen.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,14.770,2.554,26.247
+iphone.capitolvolkswagen.skp_tile_1024x64_,Mac_Float_Bench_32-25th,34.000,18.900,47.400
+iphone.capitolvolkswagen.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,60.320,41.272,76.352
+iphone.capitolvolkswagen.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,17.140,4.569,28.854
+iphone.capitolvolkswagen.skp_tile_256x1024_,Mac_Float_Bench_32-25th,60.000,41.000,76.000
+iphone.capitolvolkswagen.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,108.620,82.327,129.482
+iphone.capitolvolkswagen.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,30.400,15.840,43.440
+iphone.capitolvolkswagen.skp_tile_256x256_,Mac_Float_Bench_32-25th,34.000,18.900,47.400
+iphone.capitolvolkswagen.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,64.180,44.553,80.598
+iphone.capitolvolkswagen.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,17.040,4.484,28.744
+iphone.capitolvolkswagen.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,49.000,31.650,63.900
+iphone.capitolvolkswagen.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,183.200,145.720,211.520
+iphone.capitolvolkswagen.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,26.830,12.805,39.513
+iphone.capitolvolkswagen.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,26.160,12.236,38.776
+iphone.capitolvolkswagen.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,28.000,13.800,40.800
+iphone.capitolvolkswagen.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,42.600,26.210,56.860
+iphone.capitolvolkswagen.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,13.630,1.585,24.993
+iphone.capitolvolkswagen.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,21.000,7.850,33.100
+iphone.capitolvolkswagen.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,42.790,26.371,57.069
+iphone.capitolvolkswagen.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,10.300,-1.245,21.330
+iphone.capitolvolkswagen.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,20.000,7.000,32.000
+iphone.capitolvolkswagen.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,38.150,22.427,51.965
+iphone.capitolvolkswagen.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,9.690,-1.764,20.659
+iphone.capitolvolkswagen.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,20.000,7.000,32.000
+iphone.capitolvolkswagen.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,36.380,20.923,50.018
+iphone.capitolvolkswagen.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,9.180,-2.197,20.098
+iphone.capitolvolkswagen.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,28.000,13.800,40.800
+iphone.capitolvolkswagen.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,40.350,24.297,54.385
+iphone.capitolvolkswagen.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,15.570,3.235,27.127
+iphone.capitolvolkswagen.skp_tile_512x512_,Mac_Float_Bench_32-25th,28.000,13.800,40.800
+iphone.capitolvolkswagen.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,57.210,38.629,72.931
+iphone.capitolvolkswagen.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,15.460,3.141,27.006
+iphone.capitolvolkswagen.skp_tile_64x1024_,Mac_Float_Bench_32-25th,89.000,65.650,107.900
+iphone.capitolvolkswagen.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,145.970,114.075,170.567
+iphone.capitolvolkswagen.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,45.280,28.488,59.808
+milwaukeepolicenews.skp_copy_tiles_,Mac_Float_Bench_32-25th,17.000,4.450,28.700
+milwaukeepolicenews.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,25.340,11.539,37.874
+milwaukeepolicenews.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,4.690,-6.013,15.159
+milwaukeepolicenews.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+milwaukeepolicenews.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.125,-9.893,10.138
+milwaukeepolicenews.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.019,-9.984,10.021
+milwaukeepolicenews.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+milwaukeepolicenews.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,0.216,-9.816,10.238
+milwaukeepolicenews.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.050,-9.957,10.055
+milwaukeepolicenews.skp_record_,Mac_Float_Bench_32-25th,5.000,-5.750,15.500
+milwaukeepolicenews.skp_record_,Nexus10_4-1_Float_Bench_32-25th,4.044,-6.563,14.448
+milwaukeepolicenews.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.964,-9.181,11.060
+milwaukeepolicenews.skp_record_grid_,Mac_Float_Bench_32-25th,5.000,-5.750,15.500
+milwaukeepolicenews.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,4.360,-6.294,14.796
+milwaukeepolicenews.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,1.034,-9.121,11.137
+milwaukeepolicenews.skp_record_rtree_,Mac_Float_Bench_32-25th,5.000,-5.750,15.500
+milwaukeepolicenews.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,4.493,-6.181,14.943
+milwaukeepolicenews.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,1.042,-9.114,11.147
+milwaukeepolicenews.skp_tile_1024x256_,Mac_Float_Bench_32-25th,10.000,-1.500,21.000
+milwaukeepolicenews.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,23.100,9.635,35.410
+milwaukeepolicenews.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,4.760,-5.954,15.236
+milwaukeepolicenews.skp_tile_1024x64_,Mac_Float_Bench_32-25th,12.000,0.200,23.200
+milwaukeepolicenews.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,22.800,9.380,35.080
+milwaukeepolicenews.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,5.820,-5.053,16.402
+milwaukeepolicenews.skp_tile_256x1024_,Mac_Float_Bench_32-25th,11.000,-0.650,22.100
+milwaukeepolicenews.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,24.540,10.859,36.994
+milwaukeepolicenews.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,5.190,-5.588,15.709
+milwaukeepolicenews.skp_tile_256x256_,Mac_Float_Bench_32-25th,11.000,-0.650,22.100
+milwaukeepolicenews.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,25.070,11.309,37.577
+milwaukeepolicenews.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,5.600,-5.240,16.160
+milwaukeepolicenews.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,16.000,3.600,27.600
+milwaukeepolicenews.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,42.920,26.482,57.212
+milwaukeepolicenews.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,6.710,-4.296,17.381
+milwaukeepolicenews.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,6.170,-4.756,16.787
+milwaukeepolicenews.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,10.000,-1.500,21.000
+milwaukeepolicenews.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,18.220,5.487,30.042
+milwaukeepolicenews.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,4.990,-5.758,15.489
+milwaukeepolicenews.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,7.000,-4.050,17.700
+milwaukeepolicenews.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,15.360,3.056,26.896
+milwaukeepolicenews.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.540,-6.991,13.894
+milwaukeepolicenews.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,7.000,-4.050,17.700
+milwaukeepolicenews.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,14.780,2.563,26.258
+milwaukeepolicenews.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.470,-7.050,13.817
+milwaukeepolicenews.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,7.000,-4.050,17.700
+milwaukeepolicenews.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,16.070,3.659,27.677
+milwaukeepolicenews.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.070,-7.391,13.377
+milwaukeepolicenews.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,10.000,-1.500,21.000
+milwaukeepolicenews.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,20.150,7.127,32.165
+milwaukeepolicenews.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,4.800,-5.920,15.280
+milwaukeepolicenews.skp_tile_512x512_,Mac_Float_Bench_32-25th,10.000,-1.500,21.000
+milwaukeepolicenews.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,22.620,9.227,34.882
+milwaukeepolicenews.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,4.770,-5.946,15.247
+milwaukeepolicenews.skp_tile_64x1024_,Mac_Float_Bench_32-25th,15.000,2.750,26.500
+milwaukeepolicenews.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,30.140,15.619,43.154
+milwaukeepolicenews.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,7.730,-3.429,18.503
+mlb.skp_copy_tiles_,Mac_Float_Bench_32-25th,89.000,65.650,107.900
+mlb.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,101.780,76.513,121.958
+mlb.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,21.950,8.657,34.145
+mlb.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+mlb.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.230,-9.804,10.253
+mlb.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.047,-9.960,10.052
+mlb.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+mlb.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,0.659,-9.439,10.725
+mlb.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.208,-9.823,10.228
+mlb.skp_record_,Mac_Float_Bench_32-25th,16.000,3.600,27.600
+mlb.skp_record_,Nexus10_4-1_Float_Bench_32-25th,14.661,2.462,26.127
+mlb.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,1.964,-8.331,12.160
+mlb.skp_record_grid_,Mac_Float_Bench_32-25th,17.000,4.450,28.700
+mlb.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,16.058,3.649,27.663
+mlb.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,2.194,-8.135,12.413
+mlb.skp_record_rtree_,Mac_Float_Bench_32-25th,17.000,4.450,28.700
+mlb.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,15.757,3.393,27.332
+mlb.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,4.291,-6.352,14.721
+mlb.skp_tile_1024x256_,Mac_Float_Bench_32-25th,61.000,41.850,77.100
+mlb.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,100.950,75.808,121.045
+mlb.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,26.980,12.933,39.678
+mlb.skp_tile_1024x64_,Mac_Float_Bench_32-25th,127.000,97.950,149.700
+mlb.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,186.620,148.627,215.282
+mlb.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,49.220,31.837,64.142
+mlb.skp_tile_256x1024_,Mac_Float_Bench_32-25th,286.000,233.100,324.600
+mlb.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,425.600,351.760,478.160
+mlb.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,104.140,78.519,124.554
+mlb.skp_tile_256x256_,Mac_Float_Bench_32-25th,93.000,69.050,112.300
+mlb.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,155.680,122.328,181.248
+mlb.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,39.150,23.277,53.065
+mlb.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,83.000,60.550,101.300
+mlb.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,545.480,453.658,610.028
+mlb.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,70.780,50.163,87.858
+mlb.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,69.820,49.347,86.802
+mlb.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,87.000,63.950,105.700
+mlb.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,132.870,102.939,156.157
+mlb.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,36.020,20.617,49.622
+mlb.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,59.000,40.150,74.900
+mlb.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,137.720,107.062,161.492
+mlb.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,32.510,17.633,45.761
+mlb.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,53.000,35.050,68.300
+mlb.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,118.250,90.513,140.075
+mlb.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,24.390,10.732,36.829
+mlb.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,51.000,33.350,66.100
+mlb.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,96.950,72.407,116.645
+mlb.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,23.020,9.567,35.322
+mlb.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,88.000,64.800,106.800
+mlb.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,132.580,102.693,155.838
+mlb.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,35.770,20.405,49.347
+mlb.skp_tile_512x512_,Mac_Float_Bench_32-25th,53.000,35.050,68.300
+mlb.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,106.090,80.177,126.699
+mlb.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,25.440,11.624,37.984
+mlb.skp_tile_64x1024_,Mac_Float_Bench_32-25th,511.000,424.350,572.100
+mlb.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,740.530,619.450,824.583
+mlb.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,172.720,136.812,199.992
+nofolo.skp_copy_tiles_,Mac_Float_Bench_32-25th,45.000,28.250,59.500
+nofolo.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,45.460,28.641,60.006
+nofolo.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,9.310,-2.087,20.241
+nofolo.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+nofolo.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.038,-9.968,10.042
+nofolo.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+nofolo.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.065,-9.945,10.072
+nofolo.skp_record_,Mac_Float_Bench_32-25th,22.000,8.700,34.200
+nofolo.skp_record_,Nexus10_4-1_Float_Bench_32-25th,15.988,3.590,27.587
+nofolo.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.196,-7.284,13.515
+nofolo.skp_record_grid_,Mac_Float_Bench_32-25th,23.000,9.550,35.300
+nofolo.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,16.928,4.389,28.621
+nofolo.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.331,-7.169,13.664
+nofolo.skp_record_rtree_,Mac_Float_Bench_32-25th,23.000,9.550,35.300
+nofolo.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,16.352,3.899,27.987
+nofolo.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,4.480,-6.192,14.928
+nofolo.skp_tile_1024x256_,Mac_Float_Bench_32-25th,20.000,7.000,32.000
+nofolo.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,40.550,24.467,54.605
+nofolo.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,9.780,-1.687,20.758
+nofolo.skp_tile_1024x64_,Mac_Float_Bench_32-25th,25.000,11.250,37.500
+nofolo.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,47.100,30.035,61.810
+nofolo.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,13.290,1.296,24.619
+nofolo.skp_tile_256x256_,Mac_Float_Bench_32-25th,25.000,11.250,37.500
+nofolo.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,47.490,30.367,62.239
+nofolo.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,12.880,0.948,24.168
+nofolo.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,33.000,18.050,46.300
+nofolo.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,113.070,86.109,134.377
+nofolo.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,17.350,4.748,29.085
+nofolo.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,16.400,3.940,28.040
+nofolo.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,21.000,7.850,33.100
+nofolo.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,36.290,20.846,49.919
+nofolo.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,10.690,-0.914,21.759
+nofolo.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,15.000,2.750,26.500
+nofolo.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,37.220,21.637,50.942
+nofolo.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,7.960,-3.234,18.756
+nofolo.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,16.000,3.600,27.600
+nofolo.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,35.720,20.362,49.292
+nofolo.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,5.640,-5.206,16.204
+nofolo.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,14.000,1.900,25.400
+nofolo.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,34.690,19.486,48.159
+nofolo.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,6.280,-4.662,16.908
+nofolo.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,21.000,7.850,33.100
+nofolo.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,38.590,22.802,52.449
+nofolo.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,10.200,-1.330,21.220
+nofolo.skp_tile_512x512_,Mac_Float_Bench_32-25th,21.000,7.850,33.100
+nofolo.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,45.800,28.930,60.380
+nofolo.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,10.430,-1.135,21.473
+nytimes.skp_copy_tiles_,Mac_Float_Bench_32-25th,30.000,15.500,43.000
+nytimes.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,38.920,23.082,52.812
+nytimes.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,19.870,6.890,31.857
+nytimes.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+nytimes.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.162,-9.862,10.178
+nytimes.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.023,-9.980,10.026
+nytimes.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+nytimes.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,0.840,-9.286,10.924
+nytimes.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.275,-9.766,10.303
+nytimes.skp_record_,Mac_Float_Bench_32-25th,2.000,-8.300,12.200
+nytimes.skp_record_,Nexus10_4-1_Float_Bench_32-25th,3.360,-7.144,13.696
+nytimes.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.705,-9.401,10.775
+nytimes.skp_record_grid_,Mac_Float_Bench_32-25th,3.000,-7.450,13.300
+nytimes.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,5.184,-5.594,15.702
+nytimes.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,1.064,-9.096,11.170
+nytimes.skp_record_rtree_,Mac_Float_Bench_32-25th,4.000,-6.600,14.400
+nytimes.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,5.625,-5.219,16.187
+nytimes.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,1.270,-8.920,11.397
+nytimes.skp_tile_1024x256_,Mac_Float_Bench_32-25th,15.000,2.750,26.500
+nytimes.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,34.460,19.291,47.906
+nytimes.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,16.690,4.187,28.359
+nytimes.skp_tile_1024x64_,Mac_Float_Bench_32-25th,20.000,7.000,32.000
+nytimes.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,41.320,25.122,55.452
+nytimes.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,19.830,6.855,31.813
+nytimes.skp_tile_256x1024_,Mac_Float_Bench_32-25th,18.000,5.300,29.800
+nytimes.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,48.930,31.590,63.823
+nytimes.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,16.850,4.323,28.535
+nytimes.skp_tile_256x256_,Mac_Float_Bench_32-25th,21.000,7.850,33.100
+nytimes.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,41.470,25.249,55.617
+nytimes.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,20.150,7.127,32.165
+nytimes.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,47.000,29.950,61.700
+nytimes.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,166.020,131.117,192.622
+nytimes.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,58.080,39.368,73.888
+nytimes.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,56.930,38.390,72.623
+nytimes.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,14.000,1.900,25.400
+nytimes.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,24.370,10.715,36.807
+nytimes.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,15.750,3.387,27.325
+nytimes.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,13.000,1.050,24.300
+nytimes.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,37.520,21.892,51.272
+nytimes.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,31.720,16.962,44.892
+nytimes.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,13.000,1.050,24.300
+nytimes.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,31.550,16.817,44.705
+nytimes.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,33.730,18.670,47.103
+nytimes.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,13.000,1.050,24.300
+nytimes.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,28.790,14.471,41.669
+nytimes.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,34.270,19.130,47.697
+nytimes.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,14.000,1.900,25.400
+nytimes.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,23.360,9.856,35.696
+nytimes.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,16.050,3.643,27.655
+nytimes.skp_tile_512x512_,Mac_Float_Bench_32-25th,16.000,3.600,27.600
+nytimes.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,36.190,20.761,49.809
+nytimes.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,15.520,3.192,27.072
+nytimes.skp_tile_64x1024_,Mac_Float_Bench_32-25th,38.000,22.300,51.800
+nytimes.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,79.140,57.269,97.054
+nytimes.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,26.640,12.644,39.304
+planet.mozilla.skp_copy_tiles_,Mac_Float_Bench_32-25th,844.000,707.400,938.400
+planet.mozilla.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,1004.040,843.434,1114.444
+planet.mozilla.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,182.340,144.989,210.574
+planet.mozilla.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,2.000,-8.300,12.200
+planet.mozilla.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,2.026,-8.278,12.229
+planet.mozilla.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.474,-9.597,10.521
+planet.mozilla.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,8.000,-3.200,18.800
+planet.mozilla.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,10.223,-1.310,21.246
+planet.mozilla.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.577,-6.960,13.934
+planet.mozilla.skp_record_,Mac_Float_Bench_32-25th,67.000,46.950,83.700
+planet.mozilla.skp_record_,Nexus10_4-1_Float_Bench_32-25th,73.897,52.812,91.287
+planet.mozilla.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,19.070,6.210,30.977
+planet.mozilla.skp_record_grid_,Mac_Float_Bench_32-25th,75.000,53.750,92.500
+planet.mozilla.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,85.647,62.800,104.212
+planet.mozilla.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,21.336,8.136,33.470
+planet.mozilla.skp_record_rtree_,Mac_Float_Bench_32-25th,80.000,58.000,98.000
+planet.mozilla.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,95.906,71.520,115.497
+planet.mozilla.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,25.167,11.392,37.684
+planet.mozilla.skp_tile_1024x256_,Mac_Float_Bench_32-25th,572.000,476.200,639.200
+planet.mozilla.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,1498.400,1263.640,1658.240
+planet.mozilla.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,298.450,243.682,338.295
+planet.mozilla.skp_tile_1024x64_,Mac_Float_Bench_32-25th,1293.000,1089.050,1432.300
+planet.mozilla.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,3302.270,2796.929,3642.497
+planet.mozilla.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,776.010,649.608,863.611
+planet.mozilla.skp_tile_256x1024_,Mac_Float_Bench_32-25th,611.000,509.350,682.100
+planet.mozilla.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,1855.320,1567.022,2050.852
+planet.mozilla.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,324.310,265.663,366.741
+planet.mozilla.skp_tile_256x256_,Mac_Float_Bench_32-25th,1229.000,1034.650,1361.900
+planet.mozilla.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,3318.050,2810.343,3659.855
+planet.mozilla.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,730.250,610.712,813.275
+planet.mozilla.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,1405.000,1184.250,1555.500
+planet.mozilla.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,6054.900,5136.665,6670.390
+planet.mozilla.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,927.820,778.647,1030.602
+planet.mozilla.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,924.200,775.570,1026.620
+planet.mozilla.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,292.000,238.200,331.200
+planet.mozilla.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,395.940,326.549,445.534
+planet.mozilla.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,146.690,114.686,171.359
+planet.mozilla.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,674.000,562.900,751.400
+planet.mozilla.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,2163.670,1829.120,2390.037
+planet.mozilla.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,410.270,338.729,461.297
+planet.mozilla.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,670.000,559.500,747.000
+planet.mozilla.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,2243.750,1897.188,2478.125
+planet.mozilla.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,283.920,231.332,322.312
+planet.mozilla.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,671.000,560.350,748.100
+planet.mozilla.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,2352.270,1989.429,2597.497
+planet.mozilla.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,298.310,243.564,338.141
+planet.mozilla.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,294.000,239.900,333.400
+planet.mozilla.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,402.420,332.057,452.662
+planet.mozilla.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,148.560,116.276,173.416
+planet.mozilla.skp_tile_512x512_,Mac_Float_Bench_32-25th,600.000,500.000,670.000
+planet.mozilla.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,1675.880,1414.498,1853.468
+planet.mozilla.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,318.030,260.325,359.833
+planet.mozilla.skp_tile_64x1024_,Mac_Float_Bench_32-25th,1561.000,1316.850,1727.100
+planet.mozilla.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,3974.390,3368.231,4381.829
+planet.mozilla.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,889.910,746.423,988.901
+pokemon-table.skp_copy_tiles_,Mac_Float_Bench_32-25th,289.000,235.650,327.900
+pokemon-table.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,391.090,322.426,440.199
+pokemon-table.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,84.960,62.216,103.456
+pokemon-table.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+pokemon-table.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.638,-9.458,10.702
+pokemon-table.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.086,-9.927,10.095
+pokemon-table.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,30.000,15.500,43.000
+pokemon-table.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,36.055,20.647,49.661
+pokemon-table.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,12.831,0.906,24.114
+pokemon-table.skp_record_,Mac_Float_Bench_32-25th,18.000,5.300,29.800
+pokemon-table.skp_record_,Nexus10_4-1_Float_Bench_32-25th,32.023,17.219,45.225
+pokemon-table.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,7.178,-3.899,17.896
+pokemon-table.skp_record_grid_,Mac_Float_Bench_32-25th,31.000,16.350,44.100
+pokemon-table.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,53.682,35.630,69.050
+pokemon-table.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,13.932,1.842,25.325
+pokemon-table.skp_record_rtree_,Mac_Float_Bench_32-25th,59.000,40.150,74.900
+pokemon-table.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,84.947,62.205,103.442
+pokemon-table.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,25.865,11.986,38.452
+pokemon-table.skp_tile_1024x256_,Mac_Float_Bench_32-25th,274.000,222.900,311.400
+pokemon-table.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,675.570,564.235,753.127
+pokemon-table.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,153.120,120.152,178.432
+pokemon-table.skp_tile_1024x64_,Mac_Float_Bench_32-25th,828.000,693.800,920.800
+pokemon-table.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,2007.490,1696.366,2218.239
+pokemon-table.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,448.280,371.038,503.108
+pokemon-table.skp_tile_256x1024_,Mac_Float_Bench_32-25th,249.000,201.650,283.900
+pokemon-table.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,603.740,503.179,674.114
+pokemon-table.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,142.680,111.278,166.948
+pokemon-table.skp_tile_256x256_,Mac_Float_Bench_32-25th,583.000,485.550,651.300
+pokemon-table.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,1383.760,1166.196,1532.136
+pokemon-table.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,315.300,258.005,356.830
+pokemon-table.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,1013.000,851.050,1124.300
+pokemon-table.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,3270.490,2769.916,3607.539
+pokemon-table.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,532.020,442.217,595.222
+pokemon-table.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,529.410,439.998,592.351
+pokemon-table.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,95.000,70.750,114.500
+pokemon-table.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,184.380,146.723,212.818
+pokemon-table.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,61.260,42.071,77.386
+pokemon-table.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,314.000,256.900,355.400
+pokemon-table.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,782.560,655.176,870.816
+pokemon-table.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,175.170,138.894,202.687
+pokemon-table.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,316.000,258.600,357.600
+pokemon-table.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,781.240,654.054,869.364
+pokemon-table.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,134.990,104.742,158.489
+pokemon-table.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,314.000,256.900,355.400
+pokemon-table.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,767.970,642.774,854.767
+pokemon-table.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,124.200,95.570,146.620
+pokemon-table.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,99.000,74.150,118.900
+pokemon-table.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,190.190,151.661,219.209
+pokemon-table.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,63.580,44.043,79.938
+pokemon-table.skp_tile_512x512_,Mac_Float_Bench_32-25th,242.000,195.700,276.200
+pokemon-table.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,606.330,505.380,676.963
+pokemon-table.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,138.160,107.436,161.976
+pokemon-table.skp_tile_64x1024_,Mac_Float_Bench_32-25th,720.000,602.000,802.000
+pokemon-table.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,1649.970,1392.475,1824.967
+pokemon-table.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,372.520,306.642,419.772
+pravda.skp_copy_tiles_,Mac_Float_Bench_32-25th,27.000,12.950,39.700
+pravda.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,35.450,20.133,48.995
+pravda.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,6.010,-4.892,16.611
+pravda.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+pravda.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.136,-9.884,10.149
+pravda.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.021,-9.982,10.024
+pravda.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+pravda.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,0.708,-9.398,10.779
+pravda.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.233,-9.802,10.257
+pravda.skp_record_,Mac_Float_Bench_32-25th,3.000,-7.450,13.300
+pravda.skp_record_,Nexus10_4-1_Float_Bench_32-25th,3.732,-6.828,14.105
+pravda.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.884,-9.249,10.972
+pravda.skp_record_grid_,Mac_Float_Bench_32-25th,4.000,-6.600,14.400
+pravda.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,5.216,-5.567,15.737
+pravda.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,1.192,-8.987,11.311
+pravda.skp_record_rtree_,Mac_Float_Bench_32-25th,4.000,-6.600,14.400
+pravda.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,5.399,-5.411,15.939
+pravda.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,1.333,-8.867,11.467
+pravda.skp_tile_1024x256_,Mac_Float_Bench_32-25th,13.000,1.050,24.300
+pravda.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,30.170,15.645,43.187
+pravda.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,6.570,-4.415,17.227
+pravda.skp_tile_1024x64_,Mac_Float_Bench_32-25th,17.000,4.450,28.700
+pravda.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,33.710,18.654,47.081
+pravda.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,8.900,-2.435,19.790
+pravda.skp_tile_256x1024_,Mac_Float_Bench_32-25th,16.000,3.600,27.600
+pravda.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,38.600,22.810,52.460
+pravda.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,7.990,-3.208,18.789
+pravda.skp_tile_256x256_,Mac_Float_Bench_32-25th,18.000,5.300,29.800
+pravda.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,34.940,19.699,48.434
+pravda.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,9.380,-2.027,20.318
+pravda.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,43.000,26.550,57.300
+pravda.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,105.920,80.032,126.512
+pravda.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,35.650,20.302,49.215
+pravda.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,34.760,19.546,48.236
+pravda.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,13.000,1.050,24.300
+pravda.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,23.200,9.720,35.520
+pravda.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,6.640,-4.356,17.304
+pravda.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,12.000,0.200,23.200
+pravda.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,24.390,10.732,36.829
+pravda.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,13.830,1.755,25.213
+pravda.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,12.000,0.200,23.200
+pravda.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,23.920,10.332,36.312
+pravda.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,9.090,-2.274,19.999
+pravda.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,11.000,-0.650,22.100
+pravda.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,26.750,12.738,39.425
+pravda.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,13.110,1.143,24.421
+pravda.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,14.000,1.900,25.400
+pravda.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,24.820,11.097,37.302
+pravda.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,6.770,-4.246,17.447
+pravda.skp_tile_512x512_,Mac_Float_Bench_32-25th,14.000,1.900,25.400
+pravda.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,31.990,17.191,45.189
+pravda.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,7.120,-3.948,17.832
+pravda.skp_tile_64x1024_,Mac_Float_Bench_32-25th,28.000,13.800,40.800
+pravda.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,56.010,37.608,71.611
+pravda.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,13.860,1.781,25.246
+sahadan.skp_copy_tiles_,Mac_Float_Bench_32-25th,30.000,15.500,43.000
+sahadan.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,39.980,23.983,53.978
+sahadan.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,8.170,-3.056,18.987
+sahadan.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+sahadan.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.329,-9.720,10.362
+sahadan.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.044,-9.963,10.048
+sahadan.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,4.000,-6.600,14.400
+sahadan.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,5.099,-5.666,15.609
+sahadan.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,1.851,-8.427,12.036
+sahadan.skp_record_,Mac_Float_Bench_32-25th,5.000,-5.750,15.500
+sahadan.skp_record_,Nexus10_4-1_Float_Bench_32-25th,7.533,-3.597,18.286
+sahadan.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,1.542,-8.690,11.696
+sahadan.skp_record_grid_,Mac_Float_Bench_32-25th,6.000,-4.900,16.600
+sahadan.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,10.754,-0.860,21.829
+sahadan.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,2.428,-7.937,12.670
+sahadan.skp_record_rtree_,Mac_Float_Bench_32-25th,10.000,-1.500,21.000
+sahadan.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,15.259,2.970,26.785
+sahadan.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,4.160,-6.464,14.576
+sahadan.skp_tile_1024x256_,Mac_Float_Bench_32-25th,18.000,5.300,29.800
+sahadan.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,34.690,19.486,48.159
+sahadan.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,8.730,-2.579,19.603
+sahadan.skp_tile_1024x64_,Mac_Float_Bench_32-25th,24.000,10.400,36.400
+sahadan.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,44.220,27.587,58.642
+sahadan.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,11.970,0.175,23.167
+sahadan.skp_tile_256x1024_,Mac_Float_Bench_32-25th,21.000,7.850,33.100
+sahadan.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,45.160,28.386,59.676
+sahadan.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,10.700,-0.905,21.770
+sahadan.skp_tile_256x256_,Mac_Float_Bench_32-25th,24.000,10.400,36.400
+sahadan.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,49.470,32.049,64.417
+sahadan.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,11.820,0.047,23.002
+sahadan.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,48.000,30.800,62.800
+sahadan.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,139.970,108.974,163.967
+sahadan.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,29.570,15.134,42.527
+sahadan.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,28.750,14.438,41.625
+sahadan.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,16.000,3.600,27.600
+sahadan.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,30.550,15.968,43.605
+sahadan.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,8.260,-2.979,19.086
+sahadan.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,13.000,1.050,24.300
+sahadan.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,35.790,20.421,49.369
+sahadan.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,12.840,0.914,24.124
+sahadan.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,14.000,1.900,25.400
+sahadan.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,27.060,13.001,39.766
+sahadan.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,9.620,-1.823,20.582
+sahadan.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,13.000,1.050,24.300
+sahadan.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,29.240,14.854,42.164
+sahadan.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,5.790,-5.079,16.369
+sahadan.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,17.000,4.450,28.700
+sahadan.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,30.350,15.797,43.385
+sahadan.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,8.400,-2.860,19.240
+sahadan.skp_tile_512x512_,Mac_Float_Bench_32-25th,18.000,5.300,29.800
+sahadan.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,40.660,24.561,54.726
+sahadan.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,9.240,-2.146,20.164
+sahadan.skp_tile_64x1024_,Mac_Float_Bench_32-25th,43.000,26.550,57.300
+sahadan.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,87.320,64.222,106.052
+sahadan.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,21.110,7.944,33.221
+sfgate.skp_copy_tiles_,Mac_Float_Bench_32-25th,48.000,30.800,62.800
+sfgate.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,54.200,36.070,69.620
+sfgate.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,32.520,17.642,45.772
+sfgate.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+sfgate.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.209,-9.822,10.230
+sfgate.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.065,-9.945,10.071
+sfgate.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+sfgate.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,1.022,-9.131,11.125
+sfgate.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.352,-9.701,10.387
+sfgate.skp_record_,Mac_Float_Bench_32-25th,25.000,11.250,37.500
+sfgate.skp_record_,Nexus10_4-1_Float_Bench_32-25th,22.113,8.796,34.325
+sfgate.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,2.909,-7.527,13.200
+sfgate.skp_record_grid_,Mac_Float_Bench_32-25th,26.000,12.100,38.600
+sfgate.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,23.636,10.090,35.999
+sfgate.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,6.369,-4.587,17.005
+sfgate.skp_record_rtree_,Mac_Float_Bench_32-25th,27.000,12.950,39.700
+sfgate.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,25.046,11.289,37.551
+sfgate.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,6.565,-4.419,17.222
+sfgate.skp_tile_1024x256_,Mac_Float_Bench_32-25th,27.000,12.950,39.700
+sfgate.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,51.490,33.767,66.639
+sfgate.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,28.940,14.599,41.834
+sfgate.skp_tile_1024x64_,Mac_Float_Bench_32-25th,37.000,21.450,50.700
+sfgate.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,67.050,46.992,83.755
+sfgate.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,35.830,20.455,49.413
+sfgate.skp_tile_256x1024_,Mac_Float_Bench_32-25th,29.000,14.650,41.900
+sfgate.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,60.500,41.425,76.550
+sfgate.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,31.170,16.495,44.287
+sfgate.skp_tile_256x256_,Mac_Float_Bench_32-25th,35.000,19.750,48.500
+sfgate.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,68.520,48.242,85.372
+sfgate.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,33.830,18.755,47.213
+sfgate.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,58.000,39.300,73.800
+sfgate.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,180.130,143.111,208.143
+sfgate.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,84.330,61.680,102.763
+sfgate.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,83.170,60.695,101.487
+sfgate.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,24.000,10.400,36.400
+sfgate.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,35.080,19.818,48.588
+sfgate.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,27.890,13.706,40.679
+sfgate.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,23.000,9.550,35.300
+sfgate.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,59.440,40.524,75.384
+sfgate.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,36.350,20.898,49.985
+sfgate.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,21.000,7.850,33.100
+sfgate.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,54.020,35.917,69.422
+sfgate.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,35.260,19.971,48.786
+sfgate.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,21.000,7.850,33.100
+sfgate.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,49.500,32.075,64.450
+sfgate.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,35.750,20.387,49.325
+sfgate.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,24.000,10.400,36.400
+sfgate.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,32.630,17.736,45.893
+sfgate.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,27.480,13.358,40.228
+sfgate.skp_tile_512x512_,Mac_Float_Bench_32-25th,27.000,12.950,39.700
+sfgate.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,54.810,36.589,70.291
+sfgate.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,29.430,15.015,42.373
+sfgate.skp_tile_64x1024_,Mac_Float_Bench_32-25th,53.000,35.050,68.300
+sfgate.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,104.750,79.037,125.225
+sfgate.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,43.540,27.009,57.894
+simsimi.skp_copy_tiles_,Mac_Float_Bench_32-25th,14.000,1.900,25.400
+simsimi.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,19.240,6.354,31.164
+simsimi.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.170,-7.306,13.487
+simsimi.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+simsimi.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.078,-9.934,10.085
+simsimi.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.010,-9.991,10.011
+simsimi.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+simsimi.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,0.123,-9.895,10.135
+simsimi.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.025,-9.979,10.027
+simsimi.skp_record_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+simsimi.skp_record_,Nexus10_4-1_Float_Bench_32-25th,1.485,-8.738,11.634
+simsimi.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.299,-9.745,10.329
+simsimi.skp_record_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+simsimi.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,1.625,-8.619,11.787
+simsimi.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.336,-9.714,10.369
+simsimi.skp_record_rtree_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+simsimi.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,1.675,-8.577,11.842
+simsimi.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.342,-9.709,10.376
+simsimi.skp_tile_1024x256_,Mac_Float_Bench_32-25th,6.000,-4.900,16.600
+simsimi.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,20.610,7.518,32.671
+simsimi.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.250,-7.238,13.575
+simsimi.skp_tile_1024x64_,Mac_Float_Bench_32-25th,7.000,-4.050,17.700
+simsimi.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,16.750,4.237,28.425
+simsimi.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.750,-6.812,14.125
+simsimi.skp_tile_256x1024_,Mac_Float_Bench_32-25th,9.000,-2.350,19.900
+simsimi.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,27.500,13.375,40.250
+simsimi.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,4.660,-6.039,15.126
+simsimi.skp_tile_256x256_,Mac_Float_Bench_32-25th,7.000,-4.050,17.700
+simsimi.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,18.580,5.793,30.438
+simsimi.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.870,-6.710,14.257
+simsimi.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,16.000,3.600,27.600
+simsimi.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,61.930,42.640,78.123
+simsimi.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,10.620,-0.973,21.682
+simsimi.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,9.390,-2.018,20.329
+simsimi.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,7.000,-4.050,17.700
+simsimi.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,18.260,5.521,30.086
+simsimi.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.860,-6.719,14.246
+simsimi.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,5.000,-5.750,15.500
+simsimi.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,14.720,2.512,26.192
+simsimi.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,2.670,-7.731,12.937
+simsimi.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,5.000,-5.750,15.500
+simsimi.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,13.700,1.645,25.070
+simsimi.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,2.190,-8.139,12.409
+simsimi.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,5.000,-5.750,15.500
+simsimi.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,15.790,3.421,27.369
+simsimi.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,2.140,-8.181,12.354
+simsimi.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,7.000,-4.050,17.700
+simsimi.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,19.180,6.303,31.098
+simsimi.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.510,-7.017,13.861
+simsimi.skp_tile_512x512_,Mac_Float_Bench_32-25th,7.000,-4.050,17.700
+simsimi.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,24.220,10.587,36.642
+simsimi.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.330,-7.170,13.663
+simsimi.skp_tile_64x1024_,Mac_Float_Bench_32-25th,11.000,-0.650,22.100
+simsimi.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,24.330,10.680,36.763
+simsimi.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,6.100,-4.815,16.710
+slashdot.skp_copy_tiles_,Mac_Float_Bench_32-25th,44.000,27.400,58.400
+slashdot.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,55.600,37.260,71.160
+slashdot.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,12.340,0.489,23.574
+slashdot.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+slashdot.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.158,-9.866,10.173
+slashdot.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.025,-9.979,10.027
+slashdot.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+slashdot.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,0.732,-9.378,10.805
+slashdot.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.241,-9.795,10.266
+slashdot.skp_record_,Mac_Float_Bench_32-25th,11.000,-0.650,22.100
+slashdot.skp_record_,Nexus10_4-1_Float_Bench_32-25th,14.645,2.448,26.109
+slashdot.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,4.077,-6.535,14.485
+slashdot.skp_record_grid_,Mac_Float_Bench_32-25th,12.000,0.200,23.200
+slashdot.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,17.325,4.726,29.058
+slashdot.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,4.430,-6.235,14.873
+slashdot.skp_record_rtree_,Mac_Float_Bench_32-25th,13.000,1.050,24.300
+slashdot.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,18.561,5.777,30.417
+slashdot.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,4.604,-6.086,15.065
+slashdot.skp_tile_1024x256_,Mac_Float_Bench_32-25th,29.000,14.650,41.900
+slashdot.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,57.220,38.637,72.942
+slashdot.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,12.940,0.999,24.234
+slashdot.skp_tile_1024x64_,Mac_Float_Bench_32-25th,36.000,20.600,49.600
+slashdot.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,63.900,44.315,80.290
+slashdot.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,16.960,4.416,28.656
+slashdot.skp_tile_256x1024_,Mac_Float_Bench_32-25th,34.000,18.900,47.400
+slashdot.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,68.910,48.573,85.801
+slashdot.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,15.790,3.421,27.369
+slashdot.skp_tile_256x256_,Mac_Float_Bench_32-25th,36.000,20.600,49.600
+slashdot.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,64.040,44.434,80.444
+slashdot.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,17.550,4.918,29.305
+slashdot.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,115.000,87.750,136.500
+slashdot.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,438.630,362.835,492.493
+slashdot.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,79.930,57.941,97.923
+slashdot.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,79.330,57.430,97.263
+slashdot.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,26.000,12.100,38.600
+slashdot.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,41.050,24.892,55.155
+slashdot.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,12.640,0.744,23.904
+slashdot.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,22.000,8.700,34.200
+slashdot.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,48.440,31.174,63.284
+slashdot.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,14.860,2.631,26.346
+slashdot.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,19.000,6.150,30.900
+slashdot.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,56.600,38.110,72.260
+slashdot.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,7.450,-3.668,18.195
+slashdot.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,20.000,7.000,32.000
+slashdot.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,61.760,42.496,77.936
+slashdot.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,8.630,-2.664,19.493
+slashdot.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,26.000,12.100,38.600
+slashdot.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,44.080,27.468,58.488
+slashdot.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,12.770,0.854,24.047
+slashdot.skp_tile_512x512_,Mac_Float_Bench_32-25th,31.000,16.350,44.100
+slashdot.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,61.430,42.215,77.573
+slashdot.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,14.090,1.976,25.499
+slashdot.skp_tile_64x1024_,Mac_Float_Bench_32-25th,56.000,37.600,71.600
+slashdot.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,103.190,77.712,123.509
+slashdot.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,27.740,13.579,40.514
+sms.personal.skp_copy_tiles_,Mac_Float_Bench_32-25th,15.000,2.750,26.500
+sms.personal.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,15.030,2.775,26.533
+sms.personal.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,2.250,-8.088,12.475
+sms.personal.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+sms.personal.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.060,-9.949,10.066
+sms.personal.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.009,-9.993,10.009
+sms.personal.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+sms.personal.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,0.190,-9.839,10.209
+sms.personal.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.057,-9.952,10.062
+sms.personal.skp_record_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+sms.personal.skp_record_,Nexus10_4-1_Float_Bench_32-25th,0.654,-9.444,10.720
+sms.personal.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.143,-9.879,10.157
+sms.personal.skp_record_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+sms.personal.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,0.987,-9.161,11.085
+sms.personal.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.210,-9.821,10.231
+sms.personal.skp_record_rtree_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+sms.personal.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,1.129,-9.040,11.242
+sms.personal.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.251,-9.787,10.276
+sms.personal.skp_tile_1024x256_,Mac_Float_Bench_32-25th,4.000,-6.600,14.400
+sms.personal.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,9.500,-1.925,20.450
+sms.personal.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,2.510,-7.867,12.761
+sms.personal.skp_tile_1024x64_,Mac_Float_Bench_32-25th,5.000,-5.750,15.500
+sms.personal.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,8.240,-2.996,19.064
+sms.personal.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,2.470,-7.901,12.717
+sms.personal.skp_tile_256x1024_,Mac_Float_Bench_32-25th,5.000,-5.750,15.500
+sms.personal.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,11.580,-0.157,22.738
+sms.personal.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.250,-7.238,13.575
+sms.personal.skp_tile_256x256_,Mac_Float_Bench_32-25th,5.000,-5.750,15.500
+sms.personal.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,10.010,-1.492,21.011
+sms.personal.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,2.800,-7.620,13.080
+sms.personal.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,16.000,3.600,27.600
+sms.personal.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,35.300,20.005,48.830
+sms.personal.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,4.410,-6.252,14.851
+sms.personal.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.660,-6.889,14.026
+sms.personal.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,4.000,-6.600,14.400
+sms.personal.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,7.210,-3.872,17.931
+sms.personal.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,2.220,-8.113,12.442
+sms.personal.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,4.000,-6.600,14.400
+sms.personal.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,9.190,-2.189,20.109
+sms.personal.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,2.080,-8.232,12.288
+sms.personal.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,3.000,-7.450,13.300
+sms.personal.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,6.670,-4.330,17.337
+sms.personal.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,1.730,-8.530,11.903
+sms.personal.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,3.000,-7.450,13.300
+sms.personal.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,6.180,-4.747,16.798
+sms.personal.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,1.390,-8.819,11.529
+sms.personal.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,4.000,-6.600,14.400
+sms.personal.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,7.650,-3.497,18.415
+sms.personal.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,2.450,-7.918,12.695
+sms.personal.skp_tile_512x512_,Mac_Float_Bench_32-25th,4.000,-6.600,14.400
+sms.personal.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,10.410,-1.152,21.451
+sms.personal.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,2.700,-7.705,12.970
+sms.personal.skp_tile_64x1024_,Mac_Float_Bench_32-25th,8.000,-3.200,18.800
+sms.personal.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,16.450,3.982,28.095
+sms.personal.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,5.030,-5.724,15.533
+spreadsheet.skp_copy_tiles_,Mac_Float_Bench_32-25th,25.000,11.250,37.500
+spreadsheet.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,38.550,22.767,52.405
+spreadsheet.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,5.600,-5.240,16.160
+spreadsheet.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+spreadsheet.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.109,-9.907,10.120
+spreadsheet.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.016,-9.987,10.017
+spreadsheet.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,2.000,-8.300,12.200
+spreadsheet.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,2.906,-7.530,13.197
+spreadsheet.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,1.006,-9.145,11.106
+spreadsheet.skp_record_,Mac_Float_Bench_32-25th,1.000,-9.150,11.100
+spreadsheet.skp_record_,Nexus10_4-1_Float_Bench_32-25th,3.216,-7.266,13.538
+spreadsheet.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.692,-9.412,10.761
+spreadsheet.skp_record_grid_,Mac_Float_Bench_32-25th,2.000,-8.300,12.200
+spreadsheet.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,5.020,-5.733,15.522
+spreadsheet.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,1.254,-8.934,11.379
+spreadsheet.skp_record_rtree_,Mac_Float_Bench_32-25th,4.000,-6.600,14.400
+spreadsheet.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,7.217,-3.865,17.939
+spreadsheet.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,2.180,-8.147,12.398
+spreadsheet.skp_tile_1024x256_,Mac_Float_Bench_32-25th,12.000,0.200,23.200
+spreadsheet.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,37.510,21.883,51.261
+spreadsheet.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,6.780,-4.237,17.458
+spreadsheet.skp_tile_1024x64_,Mac_Float_Bench_32-25th,19.000,6.150,30.900
+spreadsheet.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,39.140,23.269,53.054
+spreadsheet.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,9.970,-1.525,20.967
+spreadsheet.skp_tile_256x1024_,Mac_Float_Bench_32-25th,11.000,-0.650,22.100
+spreadsheet.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,41.260,25.071,55.386
+spreadsheet.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,6.810,-4.212,17.491
+spreadsheet.skp_tile_256x256_,Mac_Float_Bench_32-25th,15.000,2.750,26.500
+spreadsheet.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,37.600,21.960,51.360
+spreadsheet.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,8.220,-3.013,19.042
+spreadsheet.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,43.000,26.550,57.300
+spreadsheet.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,153.320,120.322,178.652
+spreadsheet.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,20.040,7.034,32.044
+spreadsheet.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,19.310,6.413,31.241
+spreadsheet.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,8.000,-3.200,18.800
+spreadsheet.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,18.000,5.300,29.800
+spreadsheet.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,4.580,-6.107,15.038
+spreadsheet.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,9.000,-2.350,19.900
+spreadsheet.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,29.260,14.871,42.186
+spreadsheet.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,4.900,-5.835,15.390
+spreadsheet.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,9.000,-2.350,19.900
+spreadsheet.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,28.360,14.106,41.196
+spreadsheet.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.800,-6.770,14.180
+spreadsheet.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,8.000,-3.200,18.800
+spreadsheet.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,21.560,8.326,33.716
+spreadsheet.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.660,-6.889,14.026
+spreadsheet.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,8.000,-3.200,18.800
+spreadsheet.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,17.400,4.790,29.140
+spreadsheet.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,4.850,-5.878,15.335
+spreadsheet.skp_tile_512x512_,Mac_Float_Bench_32-25th,11.000,-0.650,22.100
+spreadsheet.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,32.700,17.795,45.970
+spreadsheet.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,6.560,-4.424,17.216
+spreadsheet.skp_tile_64x1024_,Mac_Float_Bench_32-25th,18.000,5.300,29.800
+spreadsheet.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,43.880,27.298,58.268
+spreadsheet.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,11.100,-0.565,22.210
+techcrunch.skp_copy_tiles_,Mac_Float_Bench_32-25th,94.000,69.900,113.400
+techcrunch.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,99.870,74.889,119.857
+techcrunch.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,27.290,13.197,40.019
+techcrunch.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+techcrunch.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.193,-9.836,10.212
+techcrunch.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.071,-9.939,10.078
+techcrunch.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+techcrunch.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,0.902,-9.233,10.993
+techcrunch.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.340,-9.711,10.373
+techcrunch.skp_record_,Mac_Float_Bench_32-25th,8.000,-3.200,18.800
+techcrunch.skp_record_,Nexus10_4-1_Float_Bench_32-25th,11.632,-0.113,22.795
+techcrunch.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.096,-7.368,13.406
+techcrunch.skp_record_grid_,Mac_Float_Bench_32-25th,9.000,-2.350,19.900
+techcrunch.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,12.972,1.026,24.269
+techcrunch.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.429,-7.085,13.772
+techcrunch.skp_record_rtree_,Mac_Float_Bench_32-25th,9.000,-2.350,19.900
+techcrunch.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,12.786,0.868,24.064
+techcrunch.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.681,-6.871,14.050
+techcrunch.skp_tile_1024x256_,Mac_Float_Bench_32-25th,35.000,19.750,48.500
+techcrunch.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,72.850,51.922,90.135
+techcrunch.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,29.250,14.863,42.175
+techcrunch.skp_tile_1024x64_,Mac_Float_Bench_32-25th,45.000,28.250,59.500
+techcrunch.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,67.580,47.443,84.338
+techcrunch.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,35.650,20.302,49.215
+techcrunch.skp_tile_256x1024_,Mac_Float_Bench_32-25th,40.000,24.000,54.000
+techcrunch.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,102.470,77.099,122.717
+techcrunch.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,32.900,17.965,46.190
+techcrunch.skp_tile_256x256_,Mac_Float_Bench_32-25th,44.000,27.400,58.400
+techcrunch.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,67.050,46.992,83.755
+techcrunch.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,31.870,17.090,45.057
+techcrunch.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,55.000,36.750,70.500
+techcrunch.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,307.070,251.010,347.777
+techcrunch.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,65.240,45.454,81.764
+techcrunch.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,64.220,44.587,80.642
+techcrunch.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,34.000,18.900,47.400
+techcrunch.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,44.690,27.986,59.159
+techcrunch.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,31.170,16.495,44.287
+techcrunch.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,32.000,17.200,45.200
+techcrunch.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,57.190,38.611,72.909
+techcrunch.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,31.770,17.005,44.947
+techcrunch.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,29.000,14.650,41.900
+techcrunch.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,63.030,43.575,79.333
+techcrunch.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,31.770,17.005,44.947
+techcrunch.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,31.000,16.350,44.100
+techcrunch.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,58.490,39.717,74.339
+techcrunch.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,27.270,13.179,39.997
+techcrunch.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,34.000,18.900,47.400
+techcrunch.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,45.580,28.743,60.138
+techcrunch.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,31.780,17.013,44.958
+techcrunch.skp_tile_512x512_,Mac_Float_Bench_32-25th,36.000,20.600,49.600
+techcrunch.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,76.380,54.923,94.018
+techcrunch.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,30.060,15.551,43.066
+techcrunch.skp_tile_64x1024_,Mac_Float_Bench_32-25th,70.000,49.500,87.000
+techcrunch.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,113.100,86.135,134.410
+techcrunch.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,45.220,28.437,59.742
+theverge.skp_copy_tiles_,Mac_Float_Bench_32-25th,274.000,222.900,311.400
+theverge.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,391.770,323.004,440.947
+theverge.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,111.010,84.359,132.111
+theverge.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+theverge.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.434,-9.631,10.478
+theverge.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.088,-9.926,10.096
+theverge.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+theverge.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,1.182,-8.995,11.300
+theverge.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.349,-9.703,10.384
+theverge.skp_record_,Mac_Float_Bench_32-25th,17.000,4.450,28.700
+theverge.skp_record_,Nexus10_4-1_Float_Bench_32-25th,8.657,-2.642,19.522
+theverge.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,2.810,-7.611,13.091
+theverge.skp_record_grid_,Mac_Float_Bench_32-25th,18.000,5.300,29.800
+theverge.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,10.805,-0.816,21.885
+theverge.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.401,-7.110,13.741
+theverge.skp_record_rtree_,Mac_Float_Bench_32-25th,18.000,5.300,29.800
+theverge.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,11.480,-0.242,22.628
+theverge.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.598,-6.942,13.958
+theverge.skp_tile_1024x256_,Mac_Float_Bench_32-25th,297.000,242.450,336.700
+theverge.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,457.580,378.943,513.338
+theverge.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,126.950,97.907,149.645
+theverge.skp_tile_1024x64_,Mac_Float_Bench_32-25th,648.000,540.800,722.800
+theverge.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,946.900,794.865,1051.590
+theverge.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,253.000,205.050,288.300
+theverge.skp_tile_256x1024_,Mac_Float_Bench_32-25th,510.000,423.500,571.000
+theverge.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,742.240,620.904,826.464
+theverge.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,196.350,156.897,225.985
+theverge.skp_tile_256x256_,Mac_Float_Bench_32-25th,463.000,383.550,519.300
+theverge.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,648.840,541.514,723.724
+theverge.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,187.510,149.383,216.261
+theverge.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,212.000,170.200,243.200
+theverge.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,965.380,810.573,1071.918
+theverge.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,206.760,165.746,237.436
+theverge.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,206.410,165.448,237.051
+theverge.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,436.000,360.600,489.600
+theverge.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,679.850,567.873,757.835
+theverge.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,174.690,138.487,202.159
+theverge.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,244.000,197.400,278.400
+theverge.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,417.970,345.274,469.767
+theverge.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,118.930,91.091,140.823
+theverge.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,247.000,199.950,281.700
+theverge.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,477.590,395.951,535.349
+theverge.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,94.900,70.665,114.390
+theverge.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,261.000,211.850,297.100
+theverge.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,477.010,395.458,534.711
+theverge.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,91.400,67.690,110.540
+theverge.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,438.000,362.300,491.800
+theverge.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,665.620,555.777,742.182
+theverge.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,175.470,139.149,203.017
+theverge.skp_tile_512x512_,Mac_Float_Bench_32-25th,322.000,263.700,364.200
+theverge.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,500.210,415.178,560.231
+theverge.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,136.050,105.643,159.655
+theverge.skp_tile_64x1024_,Mac_Float_Bench_32-25th,886.000,743.100,984.600
+theverge.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,1221.320,1028.122,1353.452
+theverge.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,327.360,268.256,370.096
+transformice.skp_copy_tiles_,Mac_Float_Bench_32-25th,55.000,36.750,70.500
+transformice.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,75.730,54.371,93.303
+transformice.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,12.490,0.617,23.739
+transformice.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+transformice.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.109,-9.907,10.120
+transformice.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.023,-9.980,10.026
+transformice.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+transformice.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,0.402,-9.658,10.443
+transformice.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.131,-9.888,10.145
+transformice.skp_record_,Mac_Float_Bench_32-25th,13.000,1.050,24.300
+transformice.skp_record_,Nexus10_4-1_Float_Bench_32-25th,5.155,-5.619,15.670
+transformice.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,1.516,-8.712,11.667
+transformice.skp_record_grid_,Mac_Float_Bench_32-25th,14.000,1.900,25.400
+transformice.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,6.207,-4.724,16.828
+transformice.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,1.775,-8.491,11.952
+transformice.skp_record_rtree_,Mac_Float_Bench_32-25th,14.000,1.900,25.400
+transformice.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,6.435,-4.530,17.078
+transformice.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,1.752,-8.511,11.927
+transformice.skp_tile_1024x256_,Mac_Float_Bench_32-25th,27.000,12.950,39.700
+transformice.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,74.540,53.359,91.994
+transformice.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,12.660,0.761,23.926
+transformice.skp_tile_1024x64_,Mac_Float_Bench_32-25th,30.000,15.500,43.000
+transformice.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,64.520,44.842,80.972
+transformice.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,14.780,2.563,26.258
+transformice.skp_tile_256x1024_,Mac_Float_Bench_32-25th,31.000,16.350,44.100
+transformice.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,85.340,62.539,103.874
+transformice.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,15.380,3.073,26.918
+transformice.skp_tile_256x256_,Mac_Float_Bench_32-25th,31.000,16.350,44.100
+transformice.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,64.810,45.089,81.291
+transformice.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,15.970,3.575,27.567
+transformice.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,55.000,36.750,70.500
+transformice.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,226.040,182.134,258.644
+transformice.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,41.350,25.148,55.485
+transformice.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,40.390,24.331,54.429
+transformice.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,27.000,12.950,39.700
+transformice.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,54.600,36.410,70.060
+transformice.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,13.250,1.262,24.575
+transformice.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,23.000,9.550,35.300
+transformice.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,56.260,37.821,71.886
+transformice.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,17.700,5.045,29.470
+transformice.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,19.000,6.150,30.900
+transformice.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,59.970,40.974,75.967
+transformice.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,13.320,1.322,24.652
+transformice.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,19.000,6.150,30.900
+transformice.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,46.170,29.245,60.787
+transformice.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,10.780,-0.837,21.858
+transformice.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,28.000,13.800,40.800
+transformice.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,57.730,39.070,73.503
+transformice.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,13.340,1.339,24.674
+transformice.skp_tile_512x512_,Mac_Float_Bench_32-25th,28.000,13.800,40.800
+transformice.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,83.230,60.746,101.553
+transformice.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,13.590,1.551,24.949
+transformice.skp_tile_64x1024_,Mac_Float_Bench_32-25th,47.000,29.950,61.700
+transformice.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,94.550,70.367,114.005
+transformice.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,23.030,9.576,35.333
+uk.wsj.skp_copy_tiles_,Mac_Float_Bench_32-25th,55.000,36.750,70.500
+uk.wsj.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,90.040,66.534,109.044
+uk.wsj.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,47.140,30.069,61.854
+uk.wsj.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+uk.wsj.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.419,-9.644,10.461
+uk.wsj.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.105,-9.911,10.116
+uk.wsj.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+uk.wsj.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,1.244,-8.943,11.368
+uk.wsj.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.423,-9.641,10.465
+uk.wsj.skp_record_,Mac_Float_Bench_32-25th,47.000,29.950,61.700
+uk.wsj.skp_record_,Nexus10_4-1_Float_Bench_32-25th,43.680,27.128,58.048
+uk.wsj.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,12.444,0.578,23.689
+uk.wsj.skp_record_grid_,Mac_Float_Bench_32-25th,48.000,30.800,62.800
+uk.wsj.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,45.131,28.361,59.644
+uk.wsj.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,12.387,0.529,23.626
+uk.wsj.skp_record_rtree_,Mac_Float_Bench_32-25th,48.000,30.800,62.800
+uk.wsj.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,46.551,29.568,61.206
+uk.wsj.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,12.687,0.784,23.955
+uk.wsj.skp_tile_1024x256_,Mac_Float_Bench_32-25th,34.000,18.900,47.400
+uk.wsj.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,78.930,57.091,96.823
+uk.wsj.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,37.400,21.790,51.140
+uk.wsj.skp_tile_1024x64_,Mac_Float_Bench_32-25th,44.000,27.400,58.400
+uk.wsj.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,93.740,69.679,113.114
+uk.wsj.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,45.410,28.598,59.951
+uk.wsj.skp_tile_256x1024_,Mac_Float_Bench_32-25th,42.000,25.700,56.200
+uk.wsj.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,101.360,76.156,121.496
+uk.wsj.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,42.980,26.533,57.278
+uk.wsj.skp_tile_256x256_,Mac_Float_Bench_32-25th,42.000,25.700,56.200
+uk.wsj.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,110.150,83.627,131.165
+uk.wsj.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,43.560,27.026,57.916
+uk.wsj.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,86.000,63.100,104.600
+uk.wsj.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,390.730,322.120,439.803
+uk.wsj.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,118.200,90.470,140.020
+uk.wsj.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,116.960,89.416,138.656
+uk.wsj.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,30.000,15.500,43.000
+uk.wsj.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,67.180,47.103,83.898
+uk.wsj.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,37.870,22.189,51.657
+uk.wsj.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,27.000,12.950,39.700
+uk.wsj.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,79.810,57.838,97.791
+uk.wsj.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,45.800,28.930,60.380
+uk.wsj.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,27.000,12.950,39.700
+uk.wsj.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,99.910,74.923,119.901
+uk.wsj.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,46.980,29.933,61.678
+uk.wsj.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,25.000,11.250,37.500
+uk.wsj.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,93.620,69.577,112.982
+uk.wsj.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,43.280,26.788,57.608
+uk.wsj.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,30.000,15.500,43.000
+uk.wsj.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,68.590,48.302,85.449
+uk.wsj.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,38.570,22.785,52.427
+uk.wsj.skp_tile_512x512_,Mac_Float_Bench_32-25th,33.000,18.050,46.300
+uk.wsj.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,85.980,63.083,104.578
+uk.wsj.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,37.600,21.960,51.360
+uk.wsj.skp_tile_64x1024_,Mac_Float_Bench_32-25th,71.000,50.350,88.100
+uk.wsj.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,158.620,124.827,184.482
+uk.wsj.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,58.660,39.861,74.526
+vnexpress.skp_copy_tiles_,Mac_Float_Bench_32-25th,36.000,20.600,49.600
+vnexpress.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,44.920,28.182,59.412
+vnexpress.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,10.100,-1.415,21.110
+vnexpress.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+vnexpress.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.297,-9.748,10.327
+vnexpress.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.077,-9.935,10.085
+vnexpress.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,1.000,-9.150,11.100
+vnexpress.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,1.426,-8.788,11.568
+vnexpress.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.476,-9.595,10.524
+vnexpress.skp_record_,Mac_Float_Bench_32-25th,14.000,1.900,25.400
+vnexpress.skp_record_,Nexus10_4-1_Float_Bench_32-25th,7.647,-3.500,18.412
+vnexpress.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,2.284,-8.058,12.513
+vnexpress.skp_record_grid_,Mac_Float_Bench_32-25th,14.000,1.900,25.400
+vnexpress.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,9.806,-1.665,20.787
+vnexpress.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,2.700,-7.705,12.970
+vnexpress.skp_record_rtree_,Mac_Float_Bench_32-25th,15.000,2.750,26.500
+vnexpress.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,10.827,-0.797,21.909
+vnexpress.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.119,-7.349,13.430
+vnexpress.skp_tile_1024x256_,Mac_Float_Bench_32-25th,21.000,7.850,33.100
+vnexpress.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,43.380,26.873,57.718
+vnexpress.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,11.220,-0.463,22.342
+vnexpress.skp_tile_1024x64_,Mac_Float_Bench_32-25th,29.000,14.650,41.900
+vnexpress.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,54.370,36.214,69.807
+vnexpress.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,16.640,4.144,28.304
+vnexpress.skp_tile_256x1024_,Mac_Float_Bench_32-25th,23.000,9.550,35.300
+vnexpress.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,52.070,34.259,67.277
+vnexpress.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,12.370,0.514,23.607
+vnexpress.skp_tile_256x256_,Mac_Float_Bench_32-25th,27.000,12.950,39.700
+vnexpress.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,50.070,32.559,65.077
+vnexpress.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,15.260,2.971,26.786
+vnexpress.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,50.000,32.500,65.000
+vnexpress.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,125.220,96.437,147.742
+vnexpress.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,47.190,30.111,61.909
+vnexpress.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,46.230,29.295,60.853
+vnexpress.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,20.000,7.000,32.000
+vnexpress.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,37.570,21.934,51.327
+vnexpress.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,11.380,-0.327,22.518
+vnexpress.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,17.000,4.450,28.700
+vnexpress.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,46.210,29.279,60.831
+vnexpress.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,29.440,15.024,42.384
+vnexpress.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,18.000,5.300,29.800
+vnexpress.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,37.000,21.450,50.700
+vnexpress.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,22.400,9.040,34.640
+vnexpress.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,16.000,3.600,27.600
+vnexpress.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,42.950,26.508,57.245
+vnexpress.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,21.280,8.088,33.408
+vnexpress.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,20.000,7.000,32.000
+vnexpress.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,38.030,22.325,51.833
+vnexpress.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,11.280,-0.412,22.408
+vnexpress.skp_tile_512x512_,Mac_Float_Bench_32-25th,21.000,7.850,33.100
+vnexpress.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,48.630,31.336,63.493
+vnexpress.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,11.210,-0.471,22.331
+vnexpress.skp_tile_64x1024_,Mac_Float_Bench_32-25th,38.000,22.300,51.800
+vnexpress.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,74.780,53.563,92.258
+vnexpress.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,20.130,7.110,32.143
+worldjournal.skp_copy_tiles_,Mac_Float_Bench_32-25th,55.000,36.750,70.500
+worldjournal.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,78.080,56.368,95.888
+worldjournal.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,23.710,10.154,36.081
+worldjournal.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+worldjournal.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.144,-9.877,10.159
+worldjournal.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.026,-9.978,10.028
+worldjournal.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+worldjournal.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,0.978,-9.168,11.076
+worldjournal.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.332,-9.718,10.366
+worldjournal.skp_record_,Mac_Float_Bench_32-25th,17.000,4.450,28.700
+worldjournal.skp_record_,Nexus10_4-1_Float_Bench_32-25th,20.476,7.405,32.524
+worldjournal.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,5.391,-5.418,15.930
+worldjournal.skp_record_grid_,Mac_Float_Bench_32-25th,18.000,5.300,29.800
+worldjournal.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,23.009,9.558,35.310
+worldjournal.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,5.806,-5.065,16.387
+worldjournal.skp_record_rtree_,Mac_Float_Bench_32-25th,18.000,5.300,29.800
+worldjournal.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,23.611,10.070,35.973
+worldjournal.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,6.068,-4.842,16.675
+worldjournal.skp_tile_1024x256_,Mac_Float_Bench_32-25th,36.000,20.600,49.600
+worldjournal.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,69.960,49.466,86.956
+worldjournal.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,21.490,8.266,33.639
+worldjournal.skp_tile_1024x64_,Mac_Float_Bench_32-25th,45.000,28.250,59.500
+worldjournal.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,80.740,58.629,98.814
+worldjournal.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,26.150,12.227,38.765
+worldjournal.skp_tile_256x1024_,Mac_Float_Bench_32-25th,44.000,27.400,58.400
+worldjournal.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,93.080,69.118,112.388
+worldjournal.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,24.580,10.893,37.038
+worldjournal.skp_tile_256x256_,Mac_Float_Bench_32-25th,48.000,30.800,62.800
+worldjournal.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,96.530,72.050,116.183
+worldjournal.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,28.150,13.927,40.965
+worldjournal.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,364.000,299.400,410.400
+worldjournal.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,2322.570,1964.185,2564.827
+worldjournal.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,280.620,228.527,318.682
+worldjournal.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,279.230,227.346,317.153
+worldjournal.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,41.000,24.850,55.100
+worldjournal.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,73.900,52.815,91.290
+worldjournal.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,26.270,12.329,38.897
+worldjournal.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,33.000,18.050,46.300
+worldjournal.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,78.190,56.462,96.009
+worldjournal.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,19.570,6.634,31.527
+worldjournal.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,28.000,13.800,40.800
+worldjournal.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,83.360,60.856,101.696
+worldjournal.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,18.920,6.082,30.812
+worldjournal.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,29.000,14.650,41.900
+worldjournal.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,83.140,60.669,101.454
+worldjournal.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,16.920,4.382,28.612
+worldjournal.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,41.000,24.850,55.100
+worldjournal.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,71.580,50.843,88.738
+worldjournal.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,26.550,12.567,39.205
+worldjournal.skp_tile_512x512_,Mac_Float_Bench_32-25th,39.000,23.150,52.900
+worldjournal.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,76.370,54.915,94.007
+worldjournal.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,22.960,9.516,35.256
+worldjournal.skp_tile_64x1024_,Mac_Float_Bench_32-25th,80.000,58.000,98.000
+worldjournal.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,147.060,115.001,171.766
+worldjournal.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,40.890,24.757,54.979
+wowwiki-pandaria.skp_copy_tiles_,Mac_Float_Bench_32-25th,264.000,214.400,300.400
+wowwiki-pandaria.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,307.090,251.026,347.799
+wowwiki-pandaria.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,98.200,73.470,118.020
+wowwiki-pandaria.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+wowwiki-pandaria.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.087,-9.926,10.096
+wowwiki-pandaria.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,4.000,-6.600,14.400
+wowwiki-pandaria.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,1.745,-8.517,11.920
+wowwiki-pandaria.skp_record_,Mac_Float_Bench_32-25th,22.000,8.700,34.200
+wowwiki-pandaria.skp_record_,Nexus10_4-1_Float_Bench_32-25th,26.216,12.284,38.838
+wowwiki-pandaria.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,6.506,-4.470,17.157
+wowwiki-pandaria.skp_record_grid_,Mac_Float_Bench_32-25th,26.000,12.100,38.600
+wowwiki-pandaria.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,34.199,19.069,47.618
+wowwiki-pandaria.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,8.397,-2.863,19.237
+wowwiki-pandaria.skp_record_rtree_,Mac_Float_Bench_32-25th,29.000,14.650,41.900
+wowwiki-pandaria.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,38.059,22.350,51.865
+wowwiki-pandaria.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,10.115,-1.402,21.127
+wowwiki-pandaria.skp_tile_1024x256_,Mac_Float_Bench_32-25th,147.000,114.950,171.700
+wowwiki-pandaria.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,322.730,264.320,365.003
+wowwiki-pandaria.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,106.170,80.245,126.787
+wowwiki-pandaria.skp_tile_1024x64_,Mac_Float_Bench_32-25th,288.000,234.800,326.800
+wowwiki-pandaria.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,531.600,441.860,594.760
+wowwiki-pandaria.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,174.580,138.393,202.038
+wowwiki-pandaria.skp_tile_256x256_,Mac_Float_Bench_32-25th,232.000,187.200,265.200
+wowwiki-pandaria.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,442.340,365.989,496.574
+wowwiki-pandaria.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,152.600,119.710,177.860
+wowwiki-pandaria.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,294.000,239.900,333.400
+wowwiki-pandaria.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,1163.330,978.830,1289.663
+wowwiki-pandaria.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,258.360,209.606,294.196
+wowwiki-pandaria.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,256.680,208.178,292.348
+wowwiki-pandaria.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,102.000,76.700,122.200
+wowwiki-pandaria.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,152.400,119.540,177.640
+wowwiki-pandaria.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,81.410,59.198,99.551
+wowwiki-pandaria.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,127.000,97.950,149.700
+wowwiki-pandaria.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,313.180,256.203,354.498
+wowwiki-pandaria.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,101.520,76.292,121.672
+wowwiki-pandaria.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,131.000,101.350,154.100
+wowwiki-pandaria.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,382.000,314.700,430.200
+wowwiki-pandaria.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,87.510,64.383,106.261
+wowwiki-pandaria.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,129.000,99.650,151.900
+wowwiki-pandaria.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,329.600,270.160,372.560
+wowwiki-pandaria.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,85.300,62.505,103.830
+wowwiki-pandaria.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,103.000,77.550,123.300
+wowwiki-pandaria.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,165.470,130.649,192.017
+wowwiki-pandaria.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,82.860,60.431,101.146
+wowwiki-pandaria.skp_tile_512x512_,Mac_Float_Bench_32-25th,140.000,109.000,164.000
+wowwiki-pandaria.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,327.710,268.553,370.481
+wowwiki-pandaria.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,101.990,76.691,122.189
+wsj.skp_copy_tiles_,Mac_Float_Bench_32-25th,66.000,46.100,82.600
+wsj.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,99.460,74.541,119.406
+wsj.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,52.960,35.016,68.256
+wsj.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+wsj.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.495,-9.579,10.544
+wsj.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.100,-9.915,10.110
+wsj.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,1.000,-9.150,11.100
+wsj.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,1.399,-8.811,11.539
+wsj.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.428,-9.636,10.471
+wsj.skp_record_,Mac_Float_Bench_32-25th,39.000,23.150,52.900
+wsj.skp_record_,Nexus10_4-1_Float_Bench_32-25th,33.695,18.641,47.065
+wsj.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,9.947,-1.545,20.942
+wsj.skp_record_grid_,Mac_Float_Bench_32-25th,40.000,24.000,54.000
+wsj.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,36.760,21.246,50.435
+wsj.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,10.543,-1.038,21.597
+wsj.skp_record_rtree_,Mac_Float_Bench_32-25th,40.000,24.000,54.000
+wsj.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,37.198,21.618,50.918
+wsj.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,10.668,-0.933,21.734
+wsj.skp_tile_1024x256_,Mac_Float_Bench_32-25th,38.000,22.300,51.800
+wsj.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,91.690,67.936,110.859
+wsj.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,44.900,28.165,59.390
+wsj.skp_tile_1024x64_,Mac_Float_Bench_32-25th,52.000,34.200,67.200
+wsj.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,120.550,92.468,142.605
+wsj.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,53.800,35.730,69.180
+wsj.skp_tile_256x1024_,Mac_Float_Bench_32-25th,47.000,29.950,61.700
+wsj.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,111.260,84.571,132.386
+wsj.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,51.230,33.545,66.353
+wsj.skp_tile_256x256_,Mac_Float_Bench_32-25th,49.000,31.650,63.900
+wsj.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,124.410,95.748,146.851
+wsj.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,52.020,34.217,67.222
+wsj.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,98.000,73.300,117.800
+wsj.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,431.190,356.512,484.309
+wsj.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,132.120,102.302,155.332
+wsj.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,130.880,101.248,153.968
+wsj.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,34.000,18.900,47.400
+wsj.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,83.900,61.315,102.290
+wsj.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,44.200,27.570,58.620
+wsj.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,33.000,18.050,46.300
+wsj.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,100.870,75.740,120.957
+wsj.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,54.740,36.529,70.214
+wsj.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,30.000,15.500,43.000
+wsj.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,101.430,76.216,121.573
+wsj.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,52.530,34.651,67.783
+wsj.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,29.000,14.650,41.900
+wsj.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,130.560,100.976,153.616
+wsj.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,57.430,38.816,73.173
+wsj.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,34.000,18.900,47.400
+wsj.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,79.550,57.617,97.505
+wsj.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,43.740,27.179,58.114
+wsj.skp_tile_512x512_,Mac_Float_Bench_32-25th,38.000,22.300,51.800
+wsj.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,106.350,80.397,126.985
+wsj.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,45.290,28.496,59.819
+wsj.skp_tile_64x1024_,Mac_Float_Bench_32-25th,83.000,60.550,101.300
+wsj.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,179.240,142.354,207.164
+wsj.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,68.060,47.851,84.866
+xkcd.skp_copy_tiles_,Mac_Float_Bench_32-25th,25.000,11.250,37.500
+xkcd.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,35.890,20.506,49.479
+xkcd.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,5.760,-5.104,16.336
+xkcd.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+xkcd.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.095,-9.919,10.104
+xkcd.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.021,-9.982,10.023
+xkcd.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+xkcd.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,0.381,-9.677,10.419
+xkcd.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.120,-9.898,10.132
+xkcd.skp_record_,Mac_Float_Bench_32-25th,57.000,38.450,72.700
+xkcd.skp_record_,Nexus10_4-1_Float_Bench_32-25th,48.923,31.585,63.816
+xkcd.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,14.475,2.304,25.922
+xkcd.skp_record_grid_,Mac_Float_Bench_32-25th,56.000,37.600,71.600
+xkcd.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,48.224,30.991,63.047
+xkcd.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,14.123,2.005,25.536
+xkcd.skp_record_rtree_,Mac_Float_Bench_32-25th,55.000,36.750,70.500
+xkcd.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,48.520,31.242,63.373
+xkcd.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,14.220,2.087,25.642
+xkcd.skp_tile_1024x256_,Mac_Float_Bench_32-25th,14.000,1.900,25.400
+xkcd.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,30.960,16.316,44.056
+xkcd.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,5.920,-4.968,16.512
+xkcd.skp_tile_1024x64_,Mac_Float_Bench_32-25th,15.000,2.750,26.500
+xkcd.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,30.200,15.670,43.220
+xkcd.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,6.840,-4.186,17.524
+xkcd.skp_tile_256x1024_,Mac_Float_Bench_32-25th,19.000,6.150,30.900
+xkcd.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,40.440,24.374,54.484
+xkcd.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,7.310,-3.787,18.041
+xkcd.skp_tile_256x256_,Mac_Float_Bench_32-25th,18.000,5.300,29.800
+xkcd.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,39.910,23.923,53.901
+xkcd.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,7.970,-3.226,18.767
+xkcd.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,52.000,34.200,67.200
+xkcd.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,999.830,839.856,1109.813
+xkcd.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,33.440,18.424,46.784
+xkcd.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,32.500,17.625,45.750
+xkcd.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,14.000,1.900,25.400
+xkcd.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,29.620,15.177,42.582
+xkcd.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,6.640,-4.356,17.304
+xkcd.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,11.000,-0.650,22.100
+xkcd.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,26.260,12.321,38.886
+xkcd.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,5.410,-5.402,15.951
+xkcd.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,10.000,-1.500,21.000
+xkcd.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,34.230,19.095,47.653
+xkcd.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,4.480,-6.192,14.928
+xkcd.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,11.000,-0.650,22.100
+xkcd.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,27.820,13.647,40.602
+xkcd.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,4.350,-6.303,14.785
+xkcd.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,14.000,1.900,25.400
+xkcd.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,29.790,15.322,42.769
+xkcd.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,6.870,-4.160,17.557
+xkcd.skp_tile_512x512_,Mac_Float_Bench_32-25th,16.000,3.600,27.600
+xkcd.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,34.310,19.164,47.741
+xkcd.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,6.720,-4.288,17.392
+xkcd.skp_tile_64x1024_,Mac_Float_Bench_32-25th,29.000,14.650,41.900
+xkcd.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,56.490,38.017,72.139
+xkcd.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,13.770,1.704,25.147
+youtube.skp_copy_tiles_,Mac_Float_Bench_32-25th,58.000,39.300,73.800
+youtube.skp_copy_tiles_,Nexus10_4-1_Float_Bench_32-25th,71.410,50.698,88.551
+youtube.skp_copy_tiles_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,24.720,11.012,37.192
+youtube.skp_playback_creation_grid_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+youtube.skp_playback_creation_grid_,Nexus10_4-1_Float_Bench_32-25th,0.316,-9.731,10.348
+youtube.skp_playback_creation_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.056,-9.952,10.061
+youtube.skp_playback_creation_rtree_,Mac_Float_Bench_32-25th,0.000,-10.000,10.000
+youtube.skp_playback_creation_rtree_,Nexus10_4-1_Float_Bench_32-25th,0.785,-9.332,10.864
+youtube.skp_playback_creation_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,0.227,-9.807,10.250
+youtube.skp_record_,Mac_Float_Bench_32-25th,11.000,-0.650,22.100
+youtube.skp_record_,Nexus10_4-1_Float_Bench_32-25th,14.957,2.714,26.453
+youtube.skp_record_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,3.792,-6.777,14.171
+youtube.skp_record_grid_,Mac_Float_Bench_32-25th,12.000,0.200,23.200
+youtube.skp_record_grid_,Nexus10_4-1_Float_Bench_32-25th,17.481,4.859,29.229
+youtube.skp_record_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,4.093,-6.521,14.503
+youtube.skp_record_rtree_,Mac_Float_Bench_32-25th,12.000,0.200,23.200
+youtube.skp_record_rtree_,Nexus10_4-1_Float_Bench_32-25th,18.196,5.467,30.016
+youtube.skp_record_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,4.325,-6.323,14.758
+youtube.skp_tile_1024x256_,Mac_Float_Bench_32-25th,35.000,19.750,48.500
+youtube.skp_tile_1024x256_,Nexus10_4-1_Float_Bench_32-25th,65.250,45.462,81.775
+youtube.skp_tile_1024x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,27.510,13.384,40.261
+youtube.skp_tile_1024x64_,Mac_Float_Bench_32-25th,46.000,29.100,60.600
+youtube.skp_tile_1024x64_,Nexus10_4-1_Float_Bench_32-25th,81.900,59.615,100.090
+youtube.skp_tile_1024x64_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,34.270,19.130,47.697
+youtube.skp_tile_256x1024_,Mac_Float_Bench_32-25th,39.000,23.150,52.900
+youtube.skp_tile_256x1024_,Nexus10_4-1_Float_Bench_32-25th,78.450,56.683,96.295
+youtube.skp_tile_256x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,26.140,12.219,38.754
+youtube.skp_tile_256x256_,Mac_Float_Bench_32-25th,42.000,25.700,56.200
+youtube.skp_tile_256x256_,Nexus10_4-1_Float_Bench_32-25th,72.740,51.829,90.014
+youtube.skp_tile_256x256_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,31.450,16.732,44.595
+youtube.skp_tile_256x256_gpu_,Mac_Float_Bench_32-25th,158.000,124.300,183.800
+youtube.skp_tile_256x256_gpu_,Nexus10_4-1_Float_Bench_32-25th,780.130,653.111,868.143
+youtube.skp_tile_256x256_gpu_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,154.690,121.487,180.159
+youtube.skp_tile_256x256_gpu_c,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,153.650,120.602,179.015
+youtube.skp_tile_256x256_grid_,Mac_Float_Bench_32-25th,33.000,18.050,46.300
+youtube.skp_tile_256x256_grid_,Nexus10_4-1_Float_Bench_32-25th,56.710,38.203,72.381
+youtube.skp_tile_256x256_grid_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,27.340,13.239,40.074
+youtube.skp_tile_256x256_multi_2_threads_,Mac_Float_Bench_32-25th,27.000,12.950,39.700
+youtube.skp_tile_256x256_multi_2_threads_,Nexus10_4-1_Float_Bench_32-25th,66.700,46.695,83.370
+youtube.skp_tile_256x256_multi_2_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,26.270,12.329,38.897
+youtube.skp_tile_256x256_multi_3_threads_,Mac_Float_Bench_32-25th,28.000,13.800,40.800
+youtube.skp_tile_256x256_multi_3_threads_,Nexus10_4-1_Float_Bench_32-25th,73.320,52.322,90.652
+youtube.skp_tile_256x256_multi_3_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,27.210,13.128,39.931
+youtube.skp_tile_256x256_multi_4_threads_,Mac_Float_Bench_32-25th,27.000,12.950,39.700
+youtube.skp_tile_256x256_multi_4_threads_,Nexus10_4-1_Float_Bench_32-25th,70.320,49.772,87.352
+youtube.skp_tile_256x256_multi_4_threads_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,28.800,14.480,41.680
+youtube.skp_tile_256x256_rtree_,Mac_Float_Bench_32-25th,33.000,18.050,46.300
+youtube.skp_tile_256x256_rtree_,Nexus10_4-1_Float_Bench_32-25th,56.230,37.795,71.853
+youtube.skp_tile_256x256_rtree_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,27.900,13.715,40.690
+youtube.skp_tile_512x512_,Mac_Float_Bench_32-25th,35.000,19.750,48.500
+youtube.skp_tile_512x512_,Nexus10_4-1_Float_Bench_32-25th,70.020,49.517,87.022
+youtube.skp_tile_512x512_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,27.660,13.511,40.426
+youtube.skp_tile_64x1024_,Mac_Float_Bench_32-25th,62.000,42.700,78.200
+youtube.skp_tile_64x1024_,Nexus10_4-1_Float_Bench_32-25th,116.520,89.042,138.172
+youtube.skp_tile_64x1024_,Shuttle_Ubuntu12_ATI5770_Float_Bench_32-25th,38.080,22.368,51.888
diff --git a/bench/bench_util.py b/bench/bench_util.py
index 82a16f0522..39efda4084 100644
--- a/bench/bench_util.py
+++ b/bench/bench_util.py
@@ -7,9 +7,15 @@ Created on May 19, 2011
import re
import math
+# bench representation algorithm constant names
+ALGORITHM_AVERAGE = 'avg'
+ALGORITHM_MEDIAN = 'med'
+ALGORITHM_MINIMUM = 'min'
+ALGORITHM_25TH_PERCENTILE = '25th'
+
class BenchDataPoint:
"""A single data point produced by bench.
-
+
(str, str, str, float, {str:str})"""
def __init__(self, bench, config, time_type, time, settings):
self.bench = bench
@@ -17,7 +23,7 @@ class BenchDataPoint:
self.time_type = time_type
self.time = time
self.settings = settings
-
+
def __repr__(self):
return "BenchDataPoint(%s, %s, %s, %s, %s)" % (
str(self.bench),
@@ -26,19 +32,19 @@ class BenchDataPoint:
str(self.time),
str(self.settings),
)
-
+
class _ExtremeType(object):
"""Instances of this class compare greater or less than other objects."""
def __init__(self, cmpr, rep):
object.__init__(self)
self._cmpr = cmpr
self._rep = rep
-
+
def __cmp__(self, other):
if isinstance(other, self.__class__) and other._cmpr == self._cmpr:
return 0
return self._cmpr
-
+
def __repr__(self):
return self._rep
@@ -47,25 +53,24 @@ Min = _ExtremeType(-1, "Min")
class _ListAlgorithm(object):
"""Algorithm for selecting the representation value from a given list.
- representation is one of 'avg', 'min', 'med', '25th' (average, minimum,
- median, 25th percentile)"""
+ representation is one of the ALGORITHM_XXX representation types."""
def __init__(self, data, representation=None):
if not representation:
- representation = 'avg' # default algorithm is average
+ representation = ALGORITHM_AVERAGE # default algorithm
self._data = data
self._len = len(data)
- if representation == 'avg':
+ if representation == ALGORITHM_AVERAGE:
self._rep = sum(self._data) / self._len
else:
self._data.sort()
- if representation == 'min':
+ if representation == ALGORITHM_MINIMUM:
self._rep = self._data[0]
else:
# for percentiles, we use the value below which x% of values are
# found, which allows for better detection of quantum behaviors.
- if representation == 'med':
+ if representation == ALGORITHM_MEDIAN:
x = int(round(0.5 * self._len + 0.5))
- elif representation == '25th':
+ elif representation == ALGORITHM_25TH_PERCENTILE:
x = int(round(0.25 * self._len + 0.5))
else:
raise Exception("invalid representation algorithm %s!" %
@@ -75,23 +80,51 @@ class _ListAlgorithm(object):
def compute(self):
return self._rep
-def parse(settings, lines, representation='avg'):
+def _ParseAndStoreTimes(config_re, time_re, line, bench, dic,
+ representation=None):
+ """Parses given bench time line with regex and adds data to the given dic.
+ config_re: regular expression for parsing the config line.
+ time_re: regular expression for parsing bench time.
+ line: input string line to parse.
+ bench: name of bench for the time values.
+ dic: dictionary to store bench values. See bench_dic in parse() below.
+ representation: should match one of the ALGORITHM_XXX types."""
+
+ for config in re.finditer(config_re, line):
+ current_config = config.group(1)
+ if config_re.startswith(' tile_'): # per-tile bench, add name prefix
+ current_config = 'tile_' + current_config
+ times = config.group(2)
+ for new_time in re.finditer(time_re, times):
+ current_time_type = new_time.group(1)
+ iters = [float(i) for i in
+ new_time.group(2).strip().split(',')]
+ dic.setdefault(bench, {}).setdefault(current_config, {}).setdefault(
+ current_time_type, []).append(_ListAlgorithm(
+ iters, representation).compute())
+
+def parse(settings, lines, representation=None):
"""Parses bench output into a useful data structure.
-
+
({str:str}, __iter__ -> str) -> [BenchDataPoint]
- representation should match one of those defined in class _ListAlgorithm."""
-
+ representation is one of the ALGORITHM_XXX types."""
+
benches = []
current_bench = None
+ bench_dic = {} # [bench][config][time_type] -> [list of bench values]
setting_re = '([^\s=]+)(?:=(\S+))?'
settings_re = 'skia bench:((?:\s+' + setting_re + ')*)'
bench_re = 'running bench (?:\[\d+ \d+\] )?\s*(\S+)'
time_re = '(?:(\w*)msecs = )?\s*((?:\d+\.\d+)(?:,\d+\.\d+)*)'
- config_re = '(\S+): ((?:' + time_re + '\s+)+)'
-
+ # non-per-tile benches have configs that don't end with ']'
+ config_re = '(\S+[^\]]): ((?:' + time_re + '\s+)+)'
+ # per-tile bench lines are in the following format
+ tile_re = (' tile_(\S+): tile \[\d+,\d+\] out of \[\d+,\d+\]: ((?:' +
+ time_re + '\s+)+)')
+
for line in lines:
-
- #see if this line is a settings line
+
+ # see if this line is a settings line
settingsMatch = re.search(settings_re, line)
if (settingsMatch):
settings = dict(settings)
@@ -100,40 +133,41 @@ def parse(settings, lines, representation='avg'):
settings[settingMatch.group(1)] = settingMatch.group(2)
else:
settings[settingMatch.group(1)] = True
-
- #see if this line starts a new bench
+
+ # see if this line starts a new bench
new_bench = re.search(bench_re, line)
if new_bench:
current_bench = new_bench.group(1)
-
- #add configs on this line to the current bench
+
+ # add configs on this line to the bench_dic
if current_bench:
- for new_config in re.finditer(config_re, line):
- current_config = new_config.group(1)
- times = new_config.group(2)
- for new_time in re.finditer(time_re, times):
- current_time_type = new_time.group(1)
- iters = [float(i) for i in
- new_time.group(2).strip().split(',')]
- benches.append(BenchDataPoint(
- current_bench
- , current_config
- , current_time_type
- , _ListAlgorithm(iters, representation).compute()
- , settings))
-
+ for regex in [config_re, tile_re]:
+ _ParseAndStoreTimes(regex, time_re, line, current_bench,
+ bench_dic, representation)
+
+ # append benches to list, use the total time as final bench value.
+ for bench in bench_dic:
+ for config in bench_dic[bench]:
+ for time_type in bench_dic[bench][config]:
+ benches.append(BenchDataPoint(
+ bench,
+ config,
+ time_type,
+ sum(bench_dic[bench][config][time_type]),
+ settings))
+
return benches
-
+
class LinearRegression:
"""Linear regression data based on a set of data points.
-
+
([(Number,Number)])
There must be at least two points for this to make sense."""
def __init__(self, points):
n = len(points)
max_x = Min
min_x = Max
-
+
Sx = 0.0
Sy = 0.0
Sxx = 0.0
@@ -144,20 +178,20 @@ class LinearRegression:
y = point[1]
max_x = max(max_x, x)
min_x = min(min_x, x)
-
+
Sx += x
Sy += y
Sxx += x*x
Sxy += x*y
Syy += y*y
-
+
denom = n*Sxx - Sx*Sx
if (denom != 0.0):
B = (n*Sxy - Sx*Sy) / denom
else:
B = 0.0
a = (1.0/n)*(Sy - B*Sx)
-
+
se2 = 0
sB2 = 0
sa2 = 0
@@ -165,8 +199,8 @@ class LinearRegression:
se2 = (1.0/(n*(n-2)) * (n*Syy - Sy*Sy - B*B*denom))
sB2 = (n*se2) / denom
sa2 = sB2 * (1.0/n) * Sxx
-
-
+
+
self.slope = B
self.intercept = a
self.serror = math.sqrt(max(0, se2))
@@ -174,7 +208,7 @@ class LinearRegression:
self.serror_intercept = math.sqrt(max(0, sa2))
self.max_x = max_x
self.min_x = min_x
-
+
def __repr__(self):
return "LinearRegression(%s, %s, %s, %s, %s)" % (
str(self.slope),
@@ -183,7 +217,7 @@ class LinearRegression:
str(self.serror_slope),
str(self.serror_intercept),
)
-
+
def find_min_slope(self):
"""Finds the minimal slope given one standard deviation."""
slope = self.slope
@@ -192,17 +226,17 @@ class LinearRegression:
regr_start = self.min_x
regr_end = self.max_x
regr_width = regr_end - regr_start
-
+
if slope < 0:
lower_left_y = slope*regr_start + intercept - error
upper_right_y = slope*regr_end + intercept + error
return min(0, (upper_right_y - lower_left_y) / regr_width)
-
+
elif slope > 0:
upper_left_y = slope*regr_start + intercept + error
lower_right_y = slope*regr_end + intercept - error
return max(0, (lower_right_y - upper_left_y) / regr_width)
-
+
return 0
def CreateRevisionLink(revision_number):
diff --git a/bench/benchmain.cpp b/bench/benchmain.cpp
index eb8c4eda5e..6f062e4775 100644
--- a/bench/benchmain.cpp
+++ b/bench/benchmain.cpp
@@ -46,7 +46,7 @@ enum benchModes {
static void erase(SkBitmap& bm) {
if (bm.config() == SkBitmap::kA8_Config) {
- bm.eraseColor(0);
+ bm.eraseColor(SK_ColorTRANSPARENT);
} else {
bm.eraseColor(SK_ColorWHITE);
}
@@ -203,17 +203,16 @@ public:
if (!glCtx->init(width, height)) {
return false;
}
- GrPlatform3DContext ctx =
- reinterpret_cast<GrPlatform3DContext>(glCtx->gl());
- grCtx = GrContext::Create(kOpenGL_Shaders_GrEngine, ctx);
+ GrBackendContext ctx = reinterpret_cast<GrBackendContext>(glCtx->gl());
+ grCtx = GrContext::Create(kOpenGL_GrBackend, ctx);
if (NULL != grCtx) {
- GrPlatformRenderTargetDesc desc;
+ GrBackendRenderTargetDesc desc;
desc.fConfig = kSkia8888_PM_GrPixelConfig;
desc.fWidth = width;
desc.fHeight = height;
desc.fStencilBits = 8;
desc.fRenderTargetHandle = glCtx->getFBOID();
- GrRenderTarget* rt = grCtx->createPlatformRenderTarget(desc);
+ GrRenderTarget* rt = grCtx->wrapBackendRenderTarget(desc);
if (NULL == rt) {
grCtx->unref();
return false;
@@ -909,7 +908,7 @@ int tool_main(int argc, char** argv) {
return 0;
}
-#if !defined SK_BUILD_FOR_IOS
+#if !defined(SK_BUILD_FOR_IOS) && !defined(SK_BUILD_FOR_NACL)
int main(int argc, char * const argv[]) {
return tool_main(argc, (char**) argv);
}
diff --git a/bench/gen_skp_ranges.py b/bench/gen_skp_ranges.py
new file mode 100755
index 0000000000..575db375e4
--- /dev/null
+++ b/bench/gen_skp_ranges.py
@@ -0,0 +1,159 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be found
+# in the LICENSE file.
+
+""" Analyze recent SkPicture bench data, and output suggested ranges.
+
+The outputs can be edited and pasted to bench_expectations.txt to trigger
+buildbot alerts if the actual benches are out of range. Details are documented
+in the .txt file.
+
+Currently the easiest way to update bench_expectations.txt is to delete all skp
+bench lines, run this script, and redirect outputs (">>") to be added to the
+.txt file.
+TODO(bensong): find a better way for updating the bench lines in place.
+
+Note: since input data are stored in Google Storage, you will need to set up
+the corresponding library.
+See http://developers.google.com/storage/docs/gspythonlibrary for details.
+"""
+
+__author__ = 'bensong@google.com (Ben Chen)'
+
+import bench_util
+import boto
+import cStringIO
+import optparse
+import re
+import shutil
+
+from oauth2_plugin import oauth2_plugin
+
+
+# Ratios for calculating suggested picture bench upper and lower bounds.
+BENCH_UB = 1.1 # Allow for 10% room for normal variance on the up side.
+BENCH_LB = 0.85
+
+# Further allow for a fixed amount of noise. This is especially useful for
+# benches of smaller absolute value. Keeping this value small will not affect
+# performance tunings.
+BENCH_ALLOWED_NOISE = 10
+
+# List of platforms to track.
+PLATFORMS = ['Mac_Float_Bench_32',
+ 'Nexus10_4-1_Float_Bench_32',
+ 'Shuttle_Ubuntu12_ATI5770_Float_Bench_32',
+ ]
+
+# Filter for configs of no interest. They are old config names replaced by more
+# specific ones.
+CONFIGS_TO_FILTER = ['gpu', 'raster']
+
+# Template for gsutil uri.
+GOOGLE_STORAGE_URI_SCHEME = 'gs'
+URI_BUCKET = 'chromium-skia-gm'
+
+# Constants for optparse.
+USAGE_STRING = 'USAGE: %s [options]'
+HOWTO_STRING = """
+Feel free to revise PLATFORMS for your own needs. The default is the most common
+combination that we care most about. Platforms that did not run bench_pictures
+in the given revision range will not have corresponding outputs.
+Please check http://go/skpbench to choose a range that fits your needs.
+BENCH_UB, BENCH_LB and BENCH_ALLOWED_NOISE can be changed to expand or narrow
+the permitted bench ranges without triggering buidbot alerts.
+"""
+HELP_STRING = """
+Outputs expectation picture bench ranges for the latest revisions for the given
+revision range. For instance, --rev_range=6000:6000 will return only bench
+ranges for the bots that ran bench_pictures at rev 6000; --rev-range=6000:7000
+may have multiple bench data points for each bench configuration, and the code
+returns bench data for the latest revision of all available (closer to 7000).
+""" + HOWTO_STRING
+
+OPTION_REVISION_RANGE = '--rev-range'
+OPTION_REVISION_RANGE_SHORT = '-r'
+# Bench bench representation algorithm flag.
+OPTION_REPRESENTATION_ALG = '--algorithm'
+OPTION_REPRESENTATION_ALG_SHORT = '-a'
+
+# List of valid representation algorithms.
+REPRESENTATION_ALGS = ['avg', 'min', 'med', '25th']
+
+def OutputSkpBenchExpectations(rev_min, rev_max, representation_alg):
+ """Reads skp bench data from google storage, and outputs expectations.
+
+ Ignores data with revisions outside [rev_min, rev_max] integer range. For
+ bench data with multiple revisions, we use higher revisions to calculate
+ expected bench values.
+ Uses the provided representation_alg for calculating bench representations.
+ """
+ expectation_dic = {}
+ uri = boto.storage_uri(URI_BUCKET, GOOGLE_STORAGE_URI_SCHEME)
+ for obj in uri.get_bucket():
+ # Filters out non-skp-bench files.
+ if (not obj.name.startswith('perfdata/Skia_') or
+ obj.name.find('_data_skp_') < 0):
+ continue
+ # Ignores uninterested platforms.
+ platform = obj.name.split('/')[1][5:] # Removes "Skia_" prefix.
+ if platform not in PLATFORMS:
+ continue
+ # Filters by revision.
+ for rev in range(rev_min, rev_max + 1):
+ if '_r%s_' % rev not in obj.name:
+ continue
+
+ contents = cStringIO.StringIO()
+ obj.get_file(contents)
+ for point in bench_util.parse('', contents.getvalue().split('\n'),
+ representation_alg):
+ if point.config in CONFIGS_TO_FILTER:
+ continue
+ # TODO(bensong): the filtering below is only needed during skp generation
+ # system transitioning. Change it once the new system (bench name starts
+ # with http) is stable for the switch-over, and delete it once we
+ # deprecate the old ones.
+ if point.bench.startswith('http'):
+ continue
+
+ key = '%s_%s_%s,%s-%s' % (point.bench, point.config, point.time_type,
+ platform, representation_alg)
+ # It is fine to have later revisions overwrite earlier benches, since we
+ # only use the latest bench within revision range to set expectations.
+ expectation_dic[key] = point.time
+ keys = expectation_dic.keys()
+ keys.sort()
+ for key in keys:
+ bench_val = expectation_dic[key]
+ # Prints out expectation lines.
+ print '%s,%.3f,%.3f,%.3f' % (key, bench_val,
+ bench_val * BENCH_LB - BENCH_ALLOWED_NOISE,
+ bench_val * BENCH_UB + BENCH_ALLOWED_NOISE)
+
+def main():
+ """Parses flags and outputs expected Skia picture bench results."""
+ parser = optparse.OptionParser(USAGE_STRING % '%prog' + HELP_STRING)
+ parser.add_option(OPTION_REVISION_RANGE_SHORT, OPTION_REVISION_RANGE,
+ dest='rev_range',
+ help='(Mandatory) revision range separated by ":", e.g., 6000:6005')
+ parser.add_option(OPTION_REPRESENTATION_ALG_SHORT, OPTION_REPRESENTATION_ALG,
+ dest='alg', default='25th',
+ help=('Bench representation algorithm. One of '
+ '%s. Default to "25th".' % str(REPRESENTATION_ALGS)))
+ (options, args) = parser.parse_args()
+ if options.rev_range:
+ range_match = re.search('(\d+)\:(\d+)', options.rev_range)
+ if not range_match:
+ parser.error('Wrong format for rev-range [%s]' % options.rev_range)
+ else:
+ rev_min = int(range_match.group(1))
+ rev_max = int(range_match.group(2))
+ OutputSkpBenchExpectations(rev_min, rev_max, options.alg)
+ else:
+ parser.error('Please provide mandatory flag %s' % OPTION_REVISION_RANGE)
+
+
+if '__main__' == __name__:
+ main()
diff --git a/experimental/AndroidPathRenderer/AndroidPathRenderer.cpp b/experimental/AndroidPathRenderer/AndroidPathRenderer.cpp
new file mode 100644
index 0000000000..5d927e1006
--- /dev/null
+++ b/experimental/AndroidPathRenderer/AndroidPathRenderer.cpp
@@ -0,0 +1,732 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#define LOG_TAG "PathRenderer"
+#define LOG_NDEBUG 1
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#define VERTEX_DEBUG 0
+
+#include <SkPath.h>
+#include <SkPaint.h>
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <SkTypes.h>
+#include <SkTrace.h>
+#include <SkMatrix.h>
+#include <SkPoint.h>
+
+#ifdef VERBOSE
+#define ALOGV SkDebugf
+#else
+#define ALOGV(x, ...)
+#endif
+
+#include "AndroidPathRenderer.h"
+#include "Vertex.h"
+
+namespace android {
+namespace uirenderer {
+
+#define THRESHOLD 0.5f
+
+SkRect PathRenderer::ComputePathBounds(const SkPath& path, const SkPaint* paint) {
+ SkRect bounds = path.getBounds();
+ if (paint->getStyle() != SkPaint::kFill_Style) {
+ float outset = paint->getStrokeWidth() * 0.5f;
+ bounds.outset(outset, outset);
+ }
+ return bounds;
+}
+
+void computeInverseScales(const SkMatrix* transform, float &inverseScaleX, float& inverseScaleY) {
+ if (transform && transform->getType() & (SkMatrix::kScale_Mask|SkMatrix::kAffine_Mask|SkMatrix::kPerspective_Mask)) {
+ float m00 = transform->getScaleX();
+ float m01 = transform->getSkewY();
+ float m10 = transform->getSkewX();
+ float m11 = transform->getScaleY();
+ float scaleX = sqrt(m00 * m00 + m01 * m01);
+ float scaleY = sqrt(m10 * m10 + m11 * m11);
+ inverseScaleX = (scaleX != 0) ? (1.0f / scaleX) : 1.0f;
+ inverseScaleY = (scaleY != 0) ? (1.0f / scaleY) : 1.0f;
+ } else {
+ inverseScaleX = 1.0f;
+ inverseScaleY = 1.0f;
+ }
+}
+
+inline void copyVertex(Vertex* destPtr, const Vertex* srcPtr) {
+ Vertex::set(destPtr, srcPtr->position[0], srcPtr->position[1]);
+}
+
+inline void copyAlphaVertex(AlphaVertex* destPtr, const AlphaVertex* srcPtr) {
+ AlphaVertex::set(destPtr, srcPtr->position[0], srcPtr->position[1], srcPtr->alpha);
+}
+
+/**
+ * Produces a pseudo-normal for a vertex, given the normals of the two incoming lines. If the offset
+ * from each vertex in a perimeter is calculated, the resultant lines connecting the offset vertices
+ * will be offset by 1.0
+ *
+ * Note that we can't add and normalize the two vectors, that would result in a rectangle having an
+ * offset of (sqrt(2)/2, sqrt(2)/2) at each corner, instead of (1, 1)
+ *
+ * NOTE: assumes angles between normals 90 degrees or less
+ */
+inline SkVector totalOffsetFromNormals(const SkVector& normalA, const SkVector& normalB) {
+ SkVector pseudoNormal = normalA + normalB;
+ pseudoNormal.scale(1.0f / (1.0f + fabs(normalA.dot(normalB))));
+ return pseudoNormal;
+}
+
+inline void scaleOffsetForStrokeWidth(SkVector& offset, float halfStrokeWidth,
+ float inverseScaleX, float inverseScaleY) {
+ if (halfStrokeWidth == 0.0f) {
+ // hairline - compensate for scale
+ offset.fX *= 0.5f * inverseScaleX;
+ offset.fY *= 0.5f * inverseScaleY;
+ } else {
+ offset.scale(halfStrokeWidth);
+ }
+}
+
+void getFillVerticesFromPerimeter(const SkTArray<Vertex, true>& perimeter, VertexBuffer* vertexBuffer) {
+ Vertex* buffer = vertexBuffer->alloc<Vertex>(perimeter.count());
+
+ int currentIndex = 0;
+ // zig zag between all previous points on the inside of the hull to create a
+ // triangle strip that fills the hull
+ int srcAindex = 0;
+ int srcBindex = perimeter.count() - 1;
+ while (srcAindex <= srcBindex) {
+ copyVertex(&buffer[currentIndex++], &perimeter[srcAindex]);
+ if (srcAindex == srcBindex) break;
+ copyVertex(&buffer[currentIndex++], &perimeter[srcBindex]);
+ srcAindex++;
+ srcBindex--;
+ }
+}
+
+void getStrokeVerticesFromPerimeter(const SkTArray<Vertex, true>& perimeter, float halfStrokeWidth,
+ VertexBuffer* vertexBuffer, float inverseScaleX, float inverseScaleY) {
+ Vertex* buffer = vertexBuffer->alloc<Vertex>(perimeter.count() * 2 + 2);
+
+ int currentIndex = 0;
+ const Vertex* last = &(perimeter[perimeter.count() - 1]);
+ const Vertex* current = &(perimeter[0]);
+ SkVector lastNormal;
+ lastNormal.set(current->position[1] - last->position[1],
+ last->position[0] - current->position[0]);
+ lastNormal.normalize();
+ for (int i = 0; i < perimeter.count(); i++) {
+ const Vertex* next = &(perimeter[i + 1 >= perimeter.count() ? 0 : i + 1]);
+ SkVector nextNormal;
+ nextNormal.set(next->position[1] - current->position[1],
+ current->position[0] - next->position[0]);
+ nextNormal.normalize();
+
+ SkVector totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
+ scaleOffsetForStrokeWidth(totalOffset, halfStrokeWidth, inverseScaleX, inverseScaleY);
+
+ Vertex::set(&buffer[currentIndex++],
+ current->position[0] + totalOffset.fX,
+ current->position[1] + totalOffset.fY);
+
+ Vertex::set(&buffer[currentIndex++],
+ current->position[0] - totalOffset.fX,
+ current->position[1] - totalOffset.fY);
+
+ last = current;
+ current = next;
+ lastNormal = nextNormal;
+ }
+
+ // wrap around to beginning
+ copyVertex(&buffer[currentIndex++], &buffer[0]);
+ copyVertex(&buffer[currentIndex++], &buffer[1]);
+}
+
+void getStrokeVerticesFromUnclosedVertices(const SkTArray<Vertex, true>& vertices, float halfStrokeWidth,
+ VertexBuffer* vertexBuffer, float inverseScaleX, float inverseScaleY) {
+ Vertex* buffer = vertexBuffer->alloc<Vertex>(vertices.count() * 2);
+
+ int currentIndex = 0;
+ const Vertex* current = &(vertices[0]);
+ SkVector lastNormal;
+ for (int i = 0; i < vertices.count() - 1; i++) {
+ const Vertex* next = &(vertices[i + 1]);
+ SkVector nextNormal;
+ nextNormal.set(next->position[1] - current->position[1],
+ current->position[0] - next->position[0]);
+ nextNormal.normalize();
+
+ SkVector totalOffset;
+ if (i == 0) {
+ totalOffset = nextNormal;
+ } else {
+ totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
+ }
+ scaleOffsetForStrokeWidth(totalOffset, halfStrokeWidth, inverseScaleX, inverseScaleY);
+
+ Vertex::set(&buffer[currentIndex++],
+ current->position[0] + totalOffset.fX,
+ current->position[1] + totalOffset.fY);
+
+ Vertex::set(&buffer[currentIndex++],
+ current->position[0] - totalOffset.fX,
+ current->position[1] - totalOffset.fY);
+
+ current = next;
+ lastNormal = nextNormal;
+ }
+
+ SkVector totalOffset = lastNormal;
+ scaleOffsetForStrokeWidth(totalOffset, halfStrokeWidth, inverseScaleX, inverseScaleY);
+
+ Vertex::set(&buffer[currentIndex++],
+ current->position[0] + totalOffset.fX,
+ current->position[1] + totalOffset.fY);
+ Vertex::set(&buffer[currentIndex++],
+ current->position[0] - totalOffset.fX,
+ current->position[1] - totalOffset.fY);
+#if VERTEX_DEBUG
+ for (unsigned int i = 0; i < vertexBuffer.getSize(); i++) {
+ SkDebugf("point at %f %f", buffer[i].position[0], buffer[i].position[1]);
+ }
+#endif
+}
+
+void getFillVerticesFromPerimeterAA(const SkTArray<Vertex, true>& perimeter, VertexBuffer* vertexBuffer,
+ float inverseScaleX, float inverseScaleY) {
+ AlphaVertex* buffer = vertexBuffer->alloc<AlphaVertex>(perimeter.count() * 3 + 2);
+
+ // generate alpha points - fill Alpha vertex gaps in between each point with
+ // alpha 0 vertex, offset by a scaled normal.
+ int currentIndex = 0;
+ const Vertex* last = &(perimeter[perimeter.count() - 1]);
+ const Vertex* current = &(perimeter[0]);
+ SkVector lastNormal;
+ lastNormal.set(current->position[1] - last->position[1],
+ last->position[0] - current->position[0]);
+ lastNormal.normalize();
+ for (int i = 0; i < perimeter.count(); i++) {
+ const Vertex* next = &(perimeter[i + 1 >= perimeter.count() ? 0 : i + 1]);
+ SkVector nextNormal;
+ nextNormal.set(next->position[1] - current->position[1],
+ current->position[0] - next->position[0]);
+ nextNormal.normalize();
+
+ // AA point offset from original point is that point's normal, such that each side is offset
+ // by .5 pixels
+ SkVector totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
+ totalOffset.fX *= 0.5f * inverseScaleX;
+ totalOffset.fY *= 0.5f * inverseScaleY;
+
+ AlphaVertex::set(&buffer[currentIndex++],
+ current->position[0] + totalOffset.fX,
+ current->position[1] + totalOffset.fY,
+ 0.0f);
+ AlphaVertex::set(&buffer[currentIndex++],
+ current->position[0] - totalOffset.fX,
+ current->position[1] - totalOffset.fY,
+ 1.0f);
+
+ last = current;
+ current = next;
+ lastNormal = nextNormal;
+ }
+
+ // wrap around to beginning
+ copyAlphaVertex(&buffer[currentIndex++], &buffer[0]);
+ copyAlphaVertex(&buffer[currentIndex++], &buffer[1]);
+
+ // zig zag between all previous points on the inside of the hull to create a
+ // triangle strip that fills the hull, repeating the first inner point to
+ // create degenerate tris to start inside path
+ int srcAindex = 0;
+ int srcBindex = perimeter.count() - 1;
+ while (srcAindex <= srcBindex) {
+ copyAlphaVertex(&buffer[currentIndex++], &buffer[srcAindex * 2 + 1]);
+ if (srcAindex == srcBindex) break;
+ copyAlphaVertex(&buffer[currentIndex++], &buffer[srcBindex * 2 + 1]);
+ srcAindex++;
+ srcBindex--;
+ }
+
+#if VERTEX_DEBUG
+ for (unsigned int i = 0; i < vertexBuffer.getSize(); i++) {
+ SkDebugf("point at %f %f, alpha %f", buffer[i].position[0], buffer[i].position[1], buffer[i].alpha);
+ }
+#endif
+}
+
+
+void getStrokeVerticesFromUnclosedVerticesAA(const SkTArray<Vertex, true>& vertices, float halfStrokeWidth,
+ VertexBuffer* vertexBuffer, float inverseScaleX, float inverseScaleY) {
+ AlphaVertex* buffer = vertexBuffer->alloc<AlphaVertex>(6 * vertices.count() + 2);
+
+ // avoid lines smaller than hairline since they break triangle based sampling. instead reducing
+ // alpha value (TODO: support different X/Y scale)
+ float maxAlpha = 1.0f;
+ if (halfStrokeWidth != 0 && inverseScaleX == inverseScaleY &&
+ halfStrokeWidth * inverseScaleX < 0.5f) {
+ maxAlpha *= (2 * halfStrokeWidth) / inverseScaleX;
+ halfStrokeWidth = 0.0f;
+ }
+
+ // there is no outer/inner here, using them for consistency with below approach
+ int offset = 2 * (vertices.count() - 2);
+ int currentAAOuterIndex = 2;
+ int currentAAInnerIndex = 2 * offset + 5; // reversed
+ int currentStrokeIndex = currentAAInnerIndex + 7;
+
+ const Vertex* last = &(vertices[0]);
+ const Vertex* current = &(vertices[1]);
+ SkVector lastNormal;
+ lastNormal.set(current->position[1] - last->position[1],
+ last->position[0] - current->position[0]);
+ lastNormal.normalize();
+
+ {
+ // start cap
+ SkVector totalOffset = lastNormal;
+ SkVector AAOffset = totalOffset;
+ AAOffset.fX *= 0.5f * inverseScaleX;
+ AAOffset.fY *= 0.5f * inverseScaleY;
+
+ SkVector innerOffset = totalOffset;
+ scaleOffsetForStrokeWidth(innerOffset, halfStrokeWidth, inverseScaleX, inverseScaleY);
+ SkVector outerOffset = innerOffset + AAOffset;
+ innerOffset -= AAOffset;
+
+ // TODO: support square cap by changing this offset to incorporate halfStrokeWidth
+ SkVector capAAOffset;
+ capAAOffset.set(AAOffset.fY, -AAOffset.fX);
+ AlphaVertex::set(&buffer[0],
+ last->position[0] + outerOffset.fX + capAAOffset.fX,
+ last->position[1] + outerOffset.fY + capAAOffset.fY,
+ 0.0f);
+ AlphaVertex::set(&buffer[1],
+ last->position[0] + innerOffset.fX - capAAOffset.fX,
+ last->position[1] + innerOffset.fY - capAAOffset.fY,
+ maxAlpha);
+
+ AlphaVertex::set(&buffer[2 * offset + 6],
+ last->position[0] - outerOffset.fX + capAAOffset.fX,
+ last->position[1] - outerOffset.fY + capAAOffset.fY,
+ 0.0f);
+ AlphaVertex::set(&buffer[2 * offset + 7],
+ last->position[0] - innerOffset.fX - capAAOffset.fX,
+ last->position[1] - innerOffset.fY - capAAOffset.fY,
+ maxAlpha);
+ copyAlphaVertex(&buffer[2 * offset + 8], &buffer[0]);
+ copyAlphaVertex(&buffer[2 * offset + 9], &buffer[1]);
+ copyAlphaVertex(&buffer[2 * offset + 10], &buffer[1]); // degenerate tris (the only two!)
+ copyAlphaVertex(&buffer[2 * offset + 11], &buffer[2 * offset + 7]);
+ }
+
+ for (int i = 1; i < vertices.count() - 1; i++) {
+ const Vertex* next = &(vertices[i + 1]);
+ SkVector nextNormal;
+ nextNormal.set(next->position[1] - current->position[1],
+ current->position[0] - next->position[0]);
+ nextNormal.normalize();
+
+ SkVector totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
+ SkVector AAOffset = totalOffset;
+ AAOffset.fX *= 0.5f * inverseScaleX;
+ AAOffset.fY *= 0.5f * inverseScaleY;
+
+ SkVector innerOffset = totalOffset;
+ scaleOffsetForStrokeWidth(innerOffset, halfStrokeWidth, inverseScaleX, inverseScaleY);
+ SkVector outerOffset = innerOffset + AAOffset;
+ innerOffset -= AAOffset;
+
+ AlphaVertex::set(&buffer[currentAAOuterIndex++],
+ current->position[0] + outerOffset.fX,
+ current->position[1] + outerOffset.fY,
+ 0.0f);
+ AlphaVertex::set(&buffer[currentAAOuterIndex++],
+ current->position[0] + innerOffset.fX,
+ current->position[1] + innerOffset.fY,
+ maxAlpha);
+
+ AlphaVertex::set(&buffer[currentStrokeIndex++],
+ current->position[0] + innerOffset.fX,
+ current->position[1] + innerOffset.fY,
+ maxAlpha);
+ AlphaVertex::set(&buffer[currentStrokeIndex++],
+ current->position[0] - innerOffset.fX,
+ current->position[1] - innerOffset.fY,
+ maxAlpha);
+
+ AlphaVertex::set(&buffer[currentAAInnerIndex--],
+ current->position[0] - innerOffset.fX,
+ current->position[1] - innerOffset.fY,
+ maxAlpha);
+ AlphaVertex::set(&buffer[currentAAInnerIndex--],
+ current->position[0] - outerOffset.fX,
+ current->position[1] - outerOffset.fY,
+ 0.0f);
+
+ last = current;
+ current = next;
+ lastNormal = nextNormal;
+ }
+
+ {
+ // end cap
+ SkVector totalOffset = lastNormal;
+ SkVector AAOffset = totalOffset;
+ AAOffset.fX *= 0.5f * inverseScaleX;
+ AAOffset.fY *= 0.5f * inverseScaleY;
+
+ SkVector innerOffset = totalOffset;
+ scaleOffsetForStrokeWidth(innerOffset, halfStrokeWidth, inverseScaleX, inverseScaleY);
+ SkVector outerOffset = innerOffset + AAOffset;
+ innerOffset -= AAOffset;
+
+ // TODO: support square cap by changing this offset to incorporate halfStrokeWidth
+ SkVector capAAOffset;
+ capAAOffset.set(-AAOffset.fY, AAOffset.fX);
+
+ AlphaVertex::set(&buffer[offset + 2],
+ current->position[0] + outerOffset.fX + capAAOffset.fX,
+ current->position[1] + outerOffset.fY + capAAOffset.fY,
+ 0.0f);
+ AlphaVertex::set(&buffer[offset + 3],
+ current->position[0] + innerOffset.fX - capAAOffset.fX,
+ current->position[1] + innerOffset.fY - capAAOffset.fY,
+ maxAlpha);
+
+ AlphaVertex::set(&buffer[offset + 4],
+ current->position[0] - outerOffset.fX + capAAOffset.fX,
+ current->position[1] - outerOffset.fY + capAAOffset.fY,
+ 0.0f);
+ AlphaVertex::set(&buffer[offset + 5],
+ current->position[0] - innerOffset.fX - capAAOffset.fX,
+ current->position[1] - innerOffset.fY - capAAOffset.fY,
+ maxAlpha);
+
+ copyAlphaVertex(&buffer[vertexBuffer->getSize() - 2], &buffer[offset + 3]);
+ copyAlphaVertex(&buffer[vertexBuffer->getSize() - 1], &buffer[offset + 5]);
+ }
+
+#if VERTEX_DEBUG
+ for (unsigned int i = 0; i < vertexBuffer.getSize(); i++) {
+ SkDebugf("point at %f %f, alpha %f", buffer[i].position[0], buffer[i].position[1], buffer[i].alpha);
+ }
+#endif
+}
+
+
+void getStrokeVerticesFromPerimeterAA(const SkTArray<Vertex, true>& perimeter, float halfStrokeWidth,
+ VertexBuffer* vertexBuffer, float inverseScaleX, float inverseScaleY) {
+ AlphaVertex* buffer = vertexBuffer->alloc<AlphaVertex>(6 * perimeter.count() + 8);
+
+ // avoid lines smaller than hairline since they break triangle based sampling. instead reducing
+ // alpha value (TODO: support different X/Y scale)
+ float maxAlpha = 1.0f;
+ if (halfStrokeWidth != 0 && inverseScaleX == inverseScaleY &&
+ halfStrokeWidth * inverseScaleX < 0.5f) {
+ maxAlpha *= (2 * halfStrokeWidth) / inverseScaleX;
+ halfStrokeWidth = 0.0f;
+ }
+
+ int offset = 2 * perimeter.count() + 3;
+ int currentAAOuterIndex = 0;
+ int currentStrokeIndex = offset;
+ int currentAAInnerIndex = offset * 2;
+
+ const Vertex* last = &(perimeter[perimeter.count() - 1]);
+ const Vertex* current = &(perimeter[0]);
+ SkVector lastNormal;
+ lastNormal.set(current->position[1] - last->position[1],
+ last->position[0] - current->position[0]);
+ lastNormal.normalize();
+ for (int i = 0; i < perimeter.count(); i++) {
+ const Vertex* next = &(perimeter[i + 1 >= perimeter.count() ? 0 : i + 1]);
+ SkVector nextNormal;
+ nextNormal.set(next->position[1] - current->position[1],
+ current->position[0] - next->position[0]);
+ nextNormal.normalize();
+
+ SkVector totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
+ SkVector AAOffset = totalOffset;
+ AAOffset.fX *= 0.5f * inverseScaleX;
+ AAOffset.fY *= 0.5f * inverseScaleY;
+
+ SkVector innerOffset = totalOffset;
+ scaleOffsetForStrokeWidth(innerOffset, halfStrokeWidth, inverseScaleX, inverseScaleY);
+ SkVector outerOffset = innerOffset + AAOffset;
+ innerOffset -= AAOffset;
+
+ AlphaVertex::set(&buffer[currentAAOuterIndex++],
+ current->position[0] + outerOffset.fX,
+ current->position[1] + outerOffset.fY,
+ 0.0f);
+ AlphaVertex::set(&buffer[currentAAOuterIndex++],
+ current->position[0] + innerOffset.fX,
+ current->position[1] + innerOffset.fY,
+ maxAlpha);
+
+ AlphaVertex::set(&buffer[currentStrokeIndex++],
+ current->position[0] + innerOffset.fX,
+ current->position[1] + innerOffset.fY,
+ maxAlpha);
+ AlphaVertex::set(&buffer[currentStrokeIndex++],
+ current->position[0] - innerOffset.fX,
+ current->position[1] - innerOffset.fY,
+ maxAlpha);
+
+ AlphaVertex::set(&buffer[currentAAInnerIndex++],
+ current->position[0] - innerOffset.fX,
+ current->position[1] - innerOffset.fY,
+ maxAlpha);
+ AlphaVertex::set(&buffer[currentAAInnerIndex++],
+ current->position[0] - outerOffset.fX,
+ current->position[1] - outerOffset.fY,
+ 0.0f);
+
+ last = current;
+ current = next;
+ lastNormal = nextNormal;
+ }
+
+ // wrap each strip around to beginning, creating degenerate tris to bridge strips
+ copyAlphaVertex(&buffer[currentAAOuterIndex++], &buffer[0]);
+ copyAlphaVertex(&buffer[currentAAOuterIndex++], &buffer[1]);
+ copyAlphaVertex(&buffer[currentAAOuterIndex++], &buffer[1]);
+
+ copyAlphaVertex(&buffer[currentStrokeIndex++], &buffer[offset]);
+ copyAlphaVertex(&buffer[currentStrokeIndex++], &buffer[offset + 1]);
+ copyAlphaVertex(&buffer[currentStrokeIndex++], &buffer[offset + 1]);
+
+ copyAlphaVertex(&buffer[currentAAInnerIndex++], &buffer[2 * offset]);
+ copyAlphaVertex(&buffer[currentAAInnerIndex++], &buffer[2 * offset + 1]);
+ // don't need to create last degenerate tri
+
+#if VERTEX_DEBUG
+ for (unsigned int i = 0; i < vertexBuffer.getSize(); i++) {
+ SkDebugf("point at %f %f, alpha %f", buffer[i].position[0], buffer[i].position[1], buffer[i].alpha);
+ }
+#endif
+}
+
+void PathRenderer::ConvexPathVertices(const SkPath &path, const SkPaint* paint,
+ const SkMatrix* transform, VertexBuffer* vertexBuffer) {
+ SK_TRACE_EVENT0("PathRenderer::convexPathVertices");
+
+ SkPaint::Style style = paint->getStyle();
+ bool isAA = paint->isAntiAlias();
+
+ float inverseScaleX, inverseScaleY;
+ computeInverseScales(transform, inverseScaleX, inverseScaleY);
+
+ SkTArray<Vertex, true> tempVertices;
+ float threshInvScaleX = inverseScaleX;
+ float threshInvScaleY = inverseScaleY;
+ if (style == SkPaint::kStroke_Style) {
+ // alter the bezier recursion threshold values we calculate in order to compensate for
+ // expansion done after the path vertices are found
+ SkRect bounds = path.getBounds();
+ if (!bounds.isEmpty()) {
+ threshInvScaleX *= bounds.width() / (bounds.width() + paint->getStrokeWidth());
+ threshInvScaleY *= bounds.height() / (bounds.height() + paint->getStrokeWidth());
+ }
+ }
+
+ // force close if we're filling the path, since fill path expects closed perimeter.
+ bool forceClose = style != SkPaint::kStroke_Style;
+ bool wasClosed = ConvexPathPerimeterVertices(path, forceClose, threshInvScaleX * threshInvScaleX,
+ threshInvScaleY * threshInvScaleY, &tempVertices);
+
+ if (!tempVertices.count()) {
+ // path was empty, return without allocating vertex buffer
+ return;
+ }
+
+#if VERTEX_DEBUG
+ for (unsigned int i = 0; i < tempVertices.count(); i++) {
+ SkDebugf("orig path: point at %f %f", tempVertices[i].position[0], tempVertices[i].position[1]);
+ }
+#endif
+
+ if (style == SkPaint::kStroke_Style) {
+ float halfStrokeWidth = paint->getStrokeWidth() * 0.5f;
+ if (!isAA) {
+ if (wasClosed) {
+ getStrokeVerticesFromPerimeter(tempVertices, halfStrokeWidth, vertexBuffer,
+ inverseScaleX, inverseScaleY);
+ } else {
+ getStrokeVerticesFromUnclosedVertices(tempVertices, halfStrokeWidth, vertexBuffer,
+ inverseScaleX, inverseScaleY);
+ }
+
+ } else {
+ if (wasClosed) {
+ getStrokeVerticesFromPerimeterAA(tempVertices, halfStrokeWidth, vertexBuffer,
+ inverseScaleX, inverseScaleY);
+ } else {
+ getStrokeVerticesFromUnclosedVerticesAA(tempVertices, halfStrokeWidth, vertexBuffer,
+ inverseScaleX, inverseScaleY);
+ }
+ }
+ } else {
+ // For kStrokeAndFill style, the path should be adjusted externally, as it will be treated as a fill here.
+ if (!isAA) {
+ getFillVerticesFromPerimeter(tempVertices, vertexBuffer);
+ } else {
+ getFillVerticesFromPerimeterAA(tempVertices, vertexBuffer, inverseScaleX, inverseScaleY);
+ }
+ }
+}
+
+
+void pushToVector(SkTArray<Vertex, true>* vertices, float x, float y) {
+ // TODO: make this not yuck
+ vertices->push_back();
+ Vertex* newVertex = &((*vertices)[vertices->count() - 1]);
+ Vertex::set(newVertex, x, y);
+}
+
+bool PathRenderer::ConvexPathPerimeterVertices(const SkPath& path, bool forceClose,
+ float sqrInvScaleX, float sqrInvScaleY, SkTArray<Vertex, true>* outputVertices) {
+ SK_TRACE_EVENT0("PathRenderer::convexPathPerimeterVertices");
+
+
+ // TODO: to support joins other than sharp miter, join vertices should be labelled in the
+ // perimeter, or resolved into more vertices. Reconsider forceClose-ing in that case.
+ SkPath::Iter iter(path, forceClose);
+ SkPoint pts[4];
+ SkPath::Verb v;
+ Vertex* newVertex = 0;
+ while (SkPath::kDone_Verb != (v = iter.next(pts))) {
+ switch (v) {
+ case SkPath::kMove_Verb:
+ pushToVector(outputVertices, pts[0].x(), pts[0].y());
+ ALOGV("Move to pos %f %f", pts[0].x(), pts[0].y());
+ break;
+ case SkPath::kClose_Verb:
+ ALOGV("Close at pos %f %f", pts[0].x(), pts[0].y());
+ break;
+ case SkPath::kLine_Verb:
+ ALOGV("kLine_Verb %f %f -> %f %f",
+ pts[0].x(), pts[0].y(),
+ pts[1].x(), pts[1].y());
+
+ pushToVector(outputVertices, pts[1].x(), pts[1].y());
+ break;
+ case SkPath::kQuad_Verb:
+ ALOGV("kQuad_Verb");
+ RecursiveQuadraticBezierVertices(
+ pts[0].x(), pts[0].y(),
+ pts[2].x(), pts[2].y(),
+ pts[1].x(), pts[1].y(),
+ sqrInvScaleX, sqrInvScaleY, outputVertices);
+ break;
+ case SkPath::kCubic_Verb:
+ ALOGV("kCubic_Verb");
+ RecursiveCubicBezierVertices(
+ pts[0].x(), pts[0].y(),
+ pts[1].x(), pts[1].y(),
+ pts[3].x(), pts[3].y(),
+ pts[2].x(), pts[2].y(),
+ sqrInvScaleX, sqrInvScaleY, outputVertices);
+ break;
+ default:
+ break;
+ }
+ }
+
+ int size = outputVertices->count();
+ if (size >= 2 && (*outputVertices)[0].position[0] == (*outputVertices)[size - 1].position[0] &&
+ (*outputVertices)[0].position[1] == (*outputVertices)[size - 1].position[1]) {
+ outputVertices->pop_back();
+ return true;
+ }
+ return false;
+}
+
+void PathRenderer::RecursiveCubicBezierVertices(
+ float p1x, float p1y, float c1x, float c1y,
+ float p2x, float p2y, float c2x, float c2y,
+ float sqrInvScaleX, float sqrInvScaleY, SkTArray<Vertex, true>* outputVertices) {
+ float dx = p2x - p1x;
+ float dy = p2y - p1y;
+ float d1 = fabs((c1x - p2x) * dy - (c1y - p2y) * dx);
+ float d2 = fabs((c2x - p2x) * dy - (c2y - p2y) * dx);
+ float d = d1 + d2;
+
+ // multiplying by sqrInvScaleY/X equivalent to multiplying in dimensional scale factors
+
+ if (d * d < THRESHOLD * THRESHOLD * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
+ // below thresh, draw line by adding endpoint
+ pushToVector(outputVertices, p2x, p2y);
+ } else {
+ float p1c1x = (p1x + c1x) * 0.5f;
+ float p1c1y = (p1y + c1y) * 0.5f;
+ float p2c2x = (p2x + c2x) * 0.5f;
+ float p2c2y = (p2y + c2y) * 0.5f;
+
+ float c1c2x = (c1x + c2x) * 0.5f;
+ float c1c2y = (c1y + c2y) * 0.5f;
+
+ float p1c1c2x = (p1c1x + c1c2x) * 0.5f;
+ float p1c1c2y = (p1c1y + c1c2y) * 0.5f;
+
+ float p2c1c2x = (p2c2x + c1c2x) * 0.5f;
+ float p2c1c2y = (p2c2y + c1c2y) * 0.5f;
+
+ float mx = (p1c1c2x + p2c1c2x) * 0.5f;
+ float my = (p1c1c2y + p2c1c2y) * 0.5f;
+
+ RecursiveCubicBezierVertices(
+ p1x, p1y, p1c1x, p1c1y,
+ mx, my, p1c1c2x, p1c1c2y,
+ sqrInvScaleX, sqrInvScaleY, outputVertices);
+ RecursiveCubicBezierVertices(
+ mx, my, p2c1c2x, p2c1c2y,
+ p2x, p2y, p2c2x, p2c2y,
+ sqrInvScaleX, sqrInvScaleY, outputVertices);
+ }
+}
+
+void PathRenderer::RecursiveQuadraticBezierVertices(
+ float ax, float ay,
+ float bx, float by,
+ float cx, float cy,
+ float sqrInvScaleX, float sqrInvScaleY, SkTArray<Vertex, true>* outputVertices) {
+ float dx = bx - ax;
+ float dy = by - ay;
+ float d = (cx - bx) * dy - (cy - by) * dx;
+
+ if (d * d < THRESHOLD * THRESHOLD * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
+ // below thresh, draw line by adding endpoint
+ pushToVector(outputVertices, bx, by);
+ } else {
+ float acx = (ax + cx) * 0.5f;
+ float bcx = (bx + cx) * 0.5f;
+ float acy = (ay + cy) * 0.5f;
+ float bcy = (by + cy) * 0.5f;
+
+ // midpoint
+ float mx = (acx + bcx) * 0.5f;
+ float my = (acy + bcy) * 0.5f;
+
+ RecursiveQuadraticBezierVertices(ax, ay, mx, my, acx, acy,
+ sqrInvScaleX, sqrInvScaleY, outputVertices);
+ RecursiveQuadraticBezierVertices(mx, my, bx, by, bcx, bcy,
+ sqrInvScaleX, sqrInvScaleY, outputVertices);
+ }
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/experimental/AndroidPathRenderer/AndroidPathRenderer.h b/experimental/AndroidPathRenderer/AndroidPathRenderer.h
new file mode 100644
index 0000000000..64aebfa59e
--- /dev/null
+++ b/experimental/AndroidPathRenderer/AndroidPathRenderer.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef ANDROID_HWUI_PATH_RENDERER_H
+#define ANDROID_HWUI_PATH_RENDERER_H
+
+#include <SkTArray.h>
+
+#include "Vertex.h"
+
+class SkMatrix;
+
+namespace android {
+namespace uirenderer {
+
+class VertexBuffer {
+public:
+ VertexBuffer():
+ mBuffer(0),
+ mSize(0),
+ mCleanupMethod(0)
+ {}
+
+ ~VertexBuffer() {
+ if (mCleanupMethod)
+ mCleanupMethod(mBuffer);
+ }
+
+ template <class TYPE>
+ TYPE* alloc(int size) {
+ mSize = size;
+ mBuffer = (void*)new TYPE[size];
+ mCleanupMethod = &(cleanup<TYPE>);
+
+ return (TYPE*)mBuffer;
+ }
+
+ void* getBuffer() { return mBuffer; }
+ unsigned int getSize() { return mSize; }
+
+private:
+ template <class TYPE>
+ static void cleanup(void* buffer) {
+ delete[] (TYPE*)buffer;
+ }
+
+ void* mBuffer;
+ unsigned int mSize;
+ void (*mCleanupMethod)(void*);
+};
+
+class PathRenderer {
+public:
+ static SkRect ComputePathBounds(const SkPath& path, const SkPaint* paint);
+
+ static void ConvexPathVertices(const SkPath& path, const SkPaint* paint,
+ const SkMatrix* transform, VertexBuffer* vertexBuffer);
+
+private:
+ static bool ConvexPathPerimeterVertices(const SkPath &path, bool forceClose,
+ float sqrInvScaleX, float sqrInvScaleY, SkTArray<Vertex, true>* outputVertices);
+
+/*
+ endpoints a & b,
+ control c
+ */
+ static void RecursiveQuadraticBezierVertices(
+ float ax, float ay,
+ float bx, float by,
+ float cx, float cy,
+ float sqrInvScaleX, float sqrInvScaleY,
+ SkTArray<Vertex, true>* outputVertices);
+
+/*
+ endpoints p1, p2
+ control c1, c2
+ */
+ static void RecursiveCubicBezierVertices(
+ float p1x, float p1y,
+ float c1x, float c1y,
+ float p2x, float p2y,
+ float c2x, float c2y,
+ float sqrInvScaleX, float sqrInvScaleY,
+ SkTArray<Vertex, true>* outputVertices);
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_PATH_RENDERER_H
diff --git a/experimental/AndroidPathRenderer/Vertex.h b/experimental/AndroidPathRenderer/Vertex.h
new file mode 100644
index 0000000000..c8e0ba7ea1
--- /dev/null
+++ b/experimental/AndroidPathRenderer/Vertex.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef ANDROID_HWUI_VERTEX_H
+#define ANDROID_HWUI_VERTEX_H
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * Simple structure to describe a vertex with a position and a texture.
+ */
+struct Vertex {
+ float position[2];
+
+ static inline void set(Vertex* vertex, float x, float y) {
+ vertex[0].position[0] = x;
+ vertex[0].position[1] = y;
+ }
+}; // struct Vertex
+
+/**
+ * Simple structure to describe a vertex with a position and a texture.
+ */
+/*struct TextureVertex {
+ float position[2];
+ float texture[2];
+
+ static inline void set(TextureVertex* vertex, float x, float y, float u, float v) {
+ vertex[0].position[0] = x;
+ vertex[0].position[1] = y;
+ vertex[0].texture[0] = u;
+ vertex[0].texture[1] = v;
+ }
+
+ static inline void setUV(TextureVertex* vertex, float u, float v) {
+ vertex[0].texture[0] = u;
+ vertex[0].texture[1] = v;
+ }
+};*/ // struct TextureVertex
+
+/**
+ * Simple structure to describe a vertex with a position and an alpha value.
+ */
+struct AlphaVertex : Vertex {
+ float alpha;
+
+ static inline void set(AlphaVertex* vertex, float x, float y, float alpha) {
+ Vertex::set(vertex, x, y);
+ vertex[0].alpha = alpha;
+ }
+
+ static inline void setColor(AlphaVertex* vertex, float alpha) {
+ vertex[0].alpha = alpha;
+ }
+}; // struct AlphaVertex
+
+/**
+ * Simple structure to describe a vertex with a position and an alpha value.
+ */
+/*struct AAVertex : Vertex {
+ float width;
+ float length;
+
+ static inline void set(AAVertex* vertex, float x, float y, float width, float length) {
+ Vertex::set(vertex, x, y);
+ vertex[0].width = width;
+ vertex[0].length = length;
+ }
+
+ static inline void setColor(AAVertex* vertex, float width, float length) {
+ vertex[0].width = width;
+ vertex[0].length = length;
+ }
+};*/ // struct AlphaVertex
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_VERTEX_H
diff --git a/experimental/AndroidPathRenderer/cutils/compiler.h b/experimental/AndroidPathRenderer/cutils/compiler.h
new file mode 100644
index 0000000000..56c623c42b
--- /dev/null
+++ b/experimental/AndroidPathRenderer/cutils/compiler.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef ANDROID_CUTILS_COMPILER_H
+#define ANDROID_CUTILS_COMPILER_H
+
+/*
+ * helps the compiler's optimizer predicting branches
+ */
+
+#ifdef __cplusplus
+# define CC_LIKELY( exp ) (__builtin_expect( !!(exp), true ))
+# define CC_UNLIKELY( exp ) (__builtin_expect( !!(exp), false ))
+#else
+# define CC_LIKELY( exp ) (__builtin_expect( !!(exp), 1 ))
+# define CC_UNLIKELY( exp ) (__builtin_expect( !!(exp), 0 ))
+#endif
+
+/**
+ * exports marked symbols
+ *
+ * if used on a C++ class declaration, this macro must be inserted
+ * after the "class" keyword. For instance:
+ *
+ * template <typename TYPE>
+ * class ANDROID_API Singleton { }
+ */
+
+#define ANDROID_API __attribute__((visibility("default")))
+
+#endif // ANDROID_CUTILS_COMPILER_H
diff --git a/experimental/Intersection/ActiveEdge_Test.cpp b/experimental/Intersection/ActiveEdge_Test.cpp
new file mode 100755
index 0000000000..1ac340e4e0
--- /dev/null
+++ b/experimental/Intersection/ActiveEdge_Test.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "Simplify.h"
+
+namespace UnitTest {
+
+#include "EdgeWalker.cpp"
+
+} // end of UnitTest namespace
+
+#include "Intersection_Tests.h"
+
+SkPoint leftRight[][4] = {
+// equal length
+ {{10, 10}, {10, 50}, {20, 10}, {20, 50}},
+ {{10, 10}, {10, 50}, {10, 10}, {20, 50}},
+ {{10, 10}, {10, 50}, {20, 10}, {10, 50}},
+// left top higher
+ {{10, 0}, {10, 50}, {20, 10}, {20, 50}},
+ {{10, 0}, {10, 50}, {10, 10}, {20, 50}},
+ {{10, 0}, {10, 50}, {20, 10}, {10, 50}},
+ {{10, 0}, {10, 50}, {20, 10}, {10 + 0.000001f, 40}},
+// left top lower
+ {{10, 20}, {10, 50}, {20, 10}, {20, 50}},
+ {{10, 20}, {10, 50}, {10, 10}, {20, 50}},
+ {{10, 20}, {10, 50}, {20, 10}, {10, 50}},
+ {{10, 20}, {10, 50}, {20, 10}, {10 + 0.000001f, 40}},
+ {{10, 20}, {10, 50}, { 0, 0}, {50, 50}},
+// left bottom higher
+ {{10, 10}, {10, 40}, {20, 10}, {20, 50}},
+ {{10, 10}, {10, 40}, {10, 10}, {20, 50}},
+ {{10, 10}, {10, 40}, {20, 10}, {10, 50}},
+ {{10, 10}, {10, 40}, {20, 10}, { 0 + 0.000001f, 70}},
+// left bottom lower
+ {{10, 10}, {10, 60}, {20, 10}, {20, 50}},
+ {{10, 10}, {10, 60}, {10, 10}, {20, 50}},
+ {{10, 10}, {10, 60}, {20, 10}, {10 + 0.000001f, 50}},
+ {{10, 10}, {10, 60}, {20, 10}, {10 + 0.000001f, 40}},
+ {{10, 10}, {10, 60}, { 0, 0}, {20 + 0.000001f, 20}},
+};
+
+size_t leftRightCount = sizeof(leftRight) / sizeof(leftRight[0]);
+
+// older code that worked mostly
+static bool operator_less_than(const UnitTest::ActiveEdge& lh,
+ const UnitTest::ActiveEdge& rh) {
+ if ((rh.fAbove.fY - lh.fAbove.fY > lh.fBelow.fY - rh.fAbove.fY
+ && lh.fBelow.fY < rh.fBelow.fY)
+ || (lh.fAbove.fY - rh.fAbove.fY < rh.fBelow.fY - lh.fAbove.fY
+ && rh.fBelow.fY < lh.fBelow.fY)) {
+ const SkPoint& check = rh.fBelow.fY <= lh.fBelow.fY
+ && lh.fBelow != rh.fBelow ? rh.fBelow :
+ rh.fAbove;
+ return (check.fY - lh.fAbove.fY) * (lh.fBelow.fX - lh.fAbove.fX)
+ < (lh.fBelow.fY - lh.fAbove.fY) * (check.fX - lh.fAbove.fX);
+ }
+ const SkPoint& check = lh.fBelow.fY <= rh.fBelow.fY
+ && lh.fBelow != rh.fBelow ? lh.fBelow : lh.fAbove;
+ return (rh.fBelow.fY - rh.fAbove.fY) * (check.fX - rh.fAbove.fX)
+ < (check.fY - rh.fAbove.fY) * (rh.fBelow.fX - rh.fAbove.fX);
+}
+
+
+void ActiveEdge_Test() {
+ UnitTest::InEdge leftIn, rightIn;
+ UnitTest::ActiveEdge left, right;
+ left.fWorkEdge.fEdge = &leftIn;
+ right.fWorkEdge.fEdge = &rightIn;
+ for (size_t x = 0; x < leftRightCount; ++x) {
+ left.fAbove = leftRight[x][0];
+ left.fTangent = left.fBelow = leftRight[x][1];
+ right.fAbove = leftRight[x][2];
+ right.fTangent = right.fBelow = leftRight[x][3];
+ SkASSERT(left < right);
+ SkASSERT(operator_less_than(left, right));
+ SkASSERT(!(right < left));
+ SkASSERT(!operator_less_than(right, left));
+ }
+}
+
+
+
+
diff --git a/experimental/Intersection/CubicIntersection.cpp b/experimental/Intersection/CubicIntersection.cpp
new file mode 100644
index 0000000000..fcd4119097
--- /dev/null
+++ b/experimental/Intersection/CubicIntersection.cpp
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "CurveIntersection.h"
+#include "Intersections.h"
+#include "IntersectionUtilities.h"
+#include "LineIntersection.h"
+
+static const double tClipLimit = 0.8; // http://cagd.cs.byu.edu/~tom/papers/bezclip.pdf see Multiple intersections
+
+class CubicIntersections : public Intersections {
+public:
+
+CubicIntersections(const Cubic& c1, const Cubic& c2, Intersections& i)
+ : cubic1(c1)
+ , cubic2(c2)
+ , intersections(i)
+ , depth(0)
+ , splits(0) {
+}
+
+bool intersect() {
+ double minT1, minT2, maxT1, maxT2;
+ if (!bezier_clip(cubic2, cubic1, minT1, maxT1)) {
+ return false;
+ }
+ if (!bezier_clip(cubic1, cubic2, minT2, maxT2)) {
+ return false;
+ }
+ int split;
+ if (maxT1 - minT1 < maxT2 - minT2) {
+ intersections.swap();
+ minT2 = 0;
+ maxT2 = 1;
+ split = maxT1 - minT1 > tClipLimit;
+ } else {
+ minT1 = 0;
+ maxT1 = 1;
+ split = (maxT2 - minT2 > tClipLimit) << 1;
+ }
+ return chop(minT1, maxT1, minT2, maxT2, split);
+}
+
+protected:
+
+bool intersect(double minT1, double maxT1, double minT2, double maxT2) {
+ Cubic smaller, larger;
+ // FIXME: carry last subdivide and reduceOrder result with cubic
+ sub_divide(cubic1, minT1, maxT1, intersections.swapped() ? larger : smaller);
+ sub_divide(cubic2, minT2, maxT2, intersections.swapped() ? smaller : larger);
+ Cubic smallResult;
+ if (reduceOrder(smaller, smallResult,
+ kReduceOrder_NoQuadraticsAllowed) <= 2) {
+ Cubic largeResult;
+ if (reduceOrder(larger, largeResult,
+ kReduceOrder_NoQuadraticsAllowed) <= 2) {
+ const _Line& smallLine = (const _Line&) smallResult;
+ const _Line& largeLine = (const _Line&) largeResult;
+ double smallT[2];
+ double largeT[2];
+ // FIXME: this doesn't detect or deal with coincident lines
+ if (!::intersect(smallLine, largeLine, smallT, largeT)) {
+ return false;
+ }
+ if (intersections.swapped()) {
+ smallT[0] = interp(minT2, maxT2, smallT[0]);
+ largeT[0] = interp(minT1, maxT1, largeT[0]);
+ } else {
+ smallT[0] = interp(minT1, maxT1, smallT[0]);
+ largeT[0] = interp(minT2, maxT2, largeT[0]);
+ }
+ intersections.add(smallT[0], largeT[0]);
+ return true;
+ }
+ }
+ double minT, maxT;
+ if (!bezier_clip(smaller, larger, minT, maxT)) {
+ if (minT == maxT) {
+ if (intersections.swapped()) {
+ minT1 = (minT1 + maxT1) / 2;
+ minT2 = interp(minT2, maxT2, minT);
+ } else {
+ minT1 = interp(minT1, maxT1, minT);
+ minT2 = (minT2 + maxT2) / 2;
+ }
+ intersections.add(minT1, minT2);
+ return true;
+ }
+ return false;
+ }
+
+ int split;
+ if (intersections.swapped()) {
+ double newMinT1 = interp(minT1, maxT1, minT);
+ double newMaxT1 = interp(minT1, maxT1, maxT);
+ split = (newMaxT1 - newMinT1 > (maxT1 - minT1) * tClipLimit) << 1;
+#define VERBOSE 0
+#if VERBOSE
+ printf("%s d=%d s=%d new1=(%g,%g) old1=(%g,%g) split=%d\n",
+ __FUNCTION__, depth, splits, newMinT1, newMaxT1, minT1, maxT1,
+ split);
+#endif
+ minT1 = newMinT1;
+ maxT1 = newMaxT1;
+ } else {
+ double newMinT2 = interp(minT2, maxT2, minT);
+ double newMaxT2 = interp(minT2, maxT2, maxT);
+ split = newMaxT2 - newMinT2 > (maxT2 - minT2) * tClipLimit;
+#if VERBOSE
+ printf("%s d=%d s=%d new2=(%g,%g) old2=(%g,%g) split=%d\n",
+ __FUNCTION__, depth, splits, newMinT2, newMaxT2, minT2, maxT2,
+ split);
+#endif
+ minT2 = newMinT2;
+ maxT2 = newMaxT2;
+ }
+ return chop(minT1, maxT1, minT2, maxT2, split);
+}
+
+bool chop(double minT1, double maxT1, double minT2, double maxT2, int split) {
+ ++depth;
+ intersections.swap();
+ if (split) {
+ ++splits;
+ if (split & 2) {
+ double middle1 = (maxT1 + minT1) / 2;
+ intersect(minT1, middle1, minT2, maxT2);
+ intersect(middle1, maxT1, minT2, maxT2);
+ } else {
+ double middle2 = (maxT2 + minT2) / 2;
+ intersect(minT1, maxT1, minT2, middle2);
+ intersect(minT1, maxT1, middle2, maxT2);
+ }
+ --splits;
+ intersections.swap();
+ --depth;
+ return intersections.intersected();
+ }
+ bool result = intersect(minT1, maxT1, minT2, maxT2);
+ intersections.swap();
+ --depth;
+ return result;
+}
+
+private:
+
+const Cubic& cubic1;
+const Cubic& cubic2;
+Intersections& intersections;
+int depth;
+int splits;
+};
+
+bool intersect(const Cubic& c1, const Cubic& c2, Intersections& i) {
+ CubicIntersections c(c1, c2, i);
+ return c.intersect();
+}
+
diff --git a/experimental/Intersection/CubicReduceOrder.cpp b/experimental/Intersection/CubicReduceOrder.cpp
new file mode 100644
index 0000000000..294d18c440
--- /dev/null
+++ b/experimental/Intersection/CubicReduceOrder.cpp
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "CurveIntersection.h"
+#include "Extrema.h"
+#include "IntersectionUtilities.h"
+#include "LineParameters.h"
+
+static double interp_cubic_coords(const double* src, double t)
+{
+ double ab = interp(src[0], src[2], t);
+ double bc = interp(src[2], src[4], t);
+ double cd = interp(src[4], src[6], t);
+ double abc = interp(ab, bc, t);
+ double bcd = interp(bc, cd, t);
+ return interp(abc, bcd, t);
+}
+
+static int coincident_line(const Cubic& cubic, Cubic& reduction) {
+ reduction[0] = reduction[1] = cubic[0];
+ return 1;
+}
+
+static int vertical_line(const Cubic& cubic, Cubic& reduction) {
+ double tValues[2];
+ reduction[0] = cubic[0];
+ reduction[1] = cubic[3];
+ int smaller = reduction[1].y > reduction[0].y;
+ int larger = smaller ^ 1;
+ int roots = findExtrema(cubic[0].y, cubic[1].y, cubic[2].y, cubic[3].y, tValues);
+ for (int index = 0; index < roots; ++index) {
+ double yExtrema = interp_cubic_coords(&cubic[0].y, tValues[index]);
+ if (reduction[smaller].y > yExtrema) {
+ reduction[smaller].y = yExtrema;
+ continue;
+ }
+ if (reduction[larger].y < yExtrema) {
+ reduction[larger].y = yExtrema;
+ }
+ }
+ return 2;
+}
+
+static int horizontal_line(const Cubic& cubic, Cubic& reduction) {
+ double tValues[2];
+ reduction[0] = cubic[0];
+ reduction[1] = cubic[3];
+ int smaller = reduction[1].x > reduction[0].x;
+ int larger = smaller ^ 1;
+ int roots = findExtrema(cubic[0].x, cubic[1].x, cubic[2].x, cubic[3].x, tValues);
+ for (int index = 0; index < roots; ++index) {
+ double xExtrema = interp_cubic_coords(&cubic[0].x, tValues[index]);
+ if (reduction[smaller].x > xExtrema) {
+ reduction[smaller].x = xExtrema;
+ continue;
+ }
+ if (reduction[larger].x < xExtrema) {
+ reduction[larger].x = xExtrema;
+ }
+ }
+ return 2;
+}
+
+// check to see if it is a quadratic or a line
+static int check_quadratic(const Cubic& cubic, Cubic& reduction) {
+ double dx10 = cubic[1].x - cubic[0].x;
+ double dx23 = cubic[2].x - cubic[3].x;
+ double midX = cubic[0].x + dx10 * 3 / 2;
+ if (!approximately_equal(midX - cubic[3].x, dx23 * 3 / 2)) {
+ return 0;
+ }
+ double dy10 = cubic[1].y - cubic[0].y;
+ double dy23 = cubic[2].y - cubic[3].y;
+ double midY = cubic[0].y + dy10 * 3 / 2;
+ if (!approximately_equal(midY - cubic[3].y, dy23 * 3 / 2)) {
+ return 0;
+ }
+ reduction[0] = cubic[0];
+ reduction[1].x = midX;
+ reduction[1].y = midY;
+ reduction[2] = cubic[3];
+ return 3;
+}
+
+static int check_linear(const Cubic& cubic, Cubic& reduction,
+ int minX, int maxX, int minY, int maxY) {
+ int startIndex = 0;
+ int endIndex = 3;
+ while (cubic[startIndex].approximatelyEqual(cubic[endIndex])) {
+ --endIndex;
+ if (endIndex == 0) {
+ printf("%s shouldn't get here if all four points are about equal", __FUNCTION__);
+ assert(0);
+ }
+ }
+ if (!isLinear(cubic, startIndex, endIndex)) {
+ return 0;
+ }
+ // four are colinear: return line formed by outside
+ reduction[0] = cubic[0];
+ reduction[1] = cubic[3];
+ int sameSide1;
+ int sameSide2;
+ bool useX = cubic[maxX].x - cubic[minX].x >= cubic[maxY].y - cubic[minY].y;
+ if (useX) {
+ sameSide1 = sign(cubic[0].x - cubic[1].x) + sign(cubic[3].x - cubic[1].x);
+ sameSide2 = sign(cubic[0].x - cubic[2].x) + sign(cubic[3].x - cubic[2].x);
+ } else {
+ sameSide1 = sign(cubic[0].y - cubic[1].y) + sign(cubic[3].y - cubic[1].y);
+ sameSide2 = sign(cubic[0].y - cubic[2].y) + sign(cubic[3].y - cubic[2].y);
+ }
+ if (sameSide1 == sameSide2 && (sameSide1 & 3) != 2) {
+ return 2;
+ }
+ double tValues[2];
+ int roots;
+ if (useX) {
+ roots = findExtrema(cubic[0].x, cubic[1].x, cubic[2].x, cubic[3].x, tValues);
+ } else {
+ roots = findExtrema(cubic[0].y, cubic[1].y, cubic[2].y, cubic[3].y, tValues);
+ }
+ for (int index = 0; index < roots; ++index) {
+ _Point extrema;
+ extrema.x = interp_cubic_coords(&cubic[0].x, tValues[index]);
+ extrema.y = interp_cubic_coords(&cubic[0].y, tValues[index]);
+ // sameSide > 0 means mid is smaller than either [0] or [3], so replace smaller
+ int replace;
+ if (useX) {
+ if (extrema.x < cubic[0].x ^ extrema.x < cubic[3].x) {
+ continue;
+ }
+ replace = (extrema.x < cubic[0].x | extrema.x < cubic[3].x)
+ ^ (cubic[0].x < cubic[3].x);
+ } else {
+ if (extrema.y < cubic[0].y ^ extrema.y < cubic[3].y) {
+ continue;
+ }
+ replace = (extrema.y < cubic[0].y | extrema.y < cubic[3].y)
+ ^ (cubic[0].y < cubic[3].y);
+ }
+ reduction[replace] = extrema;
+ }
+ return 2;
+}
+
+bool isLinear(const Cubic& cubic, int startIndex, int endIndex) {
+ LineParameters lineParameters;
+ lineParameters.cubicEndPoints(cubic, startIndex, endIndex);
+ double normalSquared = lineParameters.normalSquared();
+ double distance[2]; // distance is not normalized
+ int mask = other_two(startIndex, endIndex);
+ int inner1 = startIndex ^ mask;
+ int inner2 = endIndex ^ mask;
+ lineParameters.controlPtDistance(cubic, inner1, inner2, distance);
+ double limit = normalSquared;
+ int index;
+ for (index = 0; index < 2; ++index) {
+ double distSq = distance[index];
+ distSq *= distSq;
+ if (approximately_greater(distSq, limit)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/* food for thought:
+http://objectmix.com/graphics/132906-fast-precision-driven-cubic-quadratic-piecewise-degree-reduction-algos-2-a.html
+
+Given points c1, c2, c3 and c4 of a cubic Bezier, the points of the
+corresponding quadratic Bezier are (given in convex combinations of
+points):
+
+q1 = (11/13)c1 + (3/13)c2 -(3/13)c3 + (2/13)c4
+q2 = -c1 + (3/2)c2 + (3/2)c3 - c4
+q3 = (2/13)c1 - (3/13)c2 + (3/13)c3 + (11/13)c4
+
+Of course, this curve does not interpolate the end-points, but it would
+be interesting to see the behaviour of such a curve in an applet.
+
+--
+Kalle Rutanen
+http://kaba.hilvi.org
+
+*/
+
+// reduce to a quadratic or smaller
+// look for identical points
+// look for all four points in a line
+ // note that three points in a line doesn't simplify a cubic
+// look for approximation with single quadratic
+ // save approximation with multiple quadratics for later
+int reduceOrder(const Cubic& cubic, Cubic& reduction, ReduceOrder_Flags allowQuadratics) {
+ int index, minX, maxX, minY, maxY;
+ int minXSet, minYSet;
+ minX = maxX = minY = maxY = 0;
+ minXSet = minYSet = 0;
+ for (index = 1; index < 4; ++index) {
+ if (cubic[minX].x > cubic[index].x) {
+ minX = index;
+ }
+ if (cubic[minY].y > cubic[index].y) {
+ minY = index;
+ }
+ if (cubic[maxX].x < cubic[index].x) {
+ maxX = index;
+ }
+ if (cubic[maxY].y < cubic[index].y) {
+ maxY = index;
+ }
+ }
+ for (index = 0; index < 4; ++index) {
+ if (approximately_equal(cubic[index].x, cubic[minX].x)) {
+ minXSet |= 1 << index;
+ }
+ if (approximately_equal(cubic[index].y, cubic[minY].y)) {
+ minYSet |= 1 << index;
+ }
+ }
+ if (minXSet == 0xF) { // test for vertical line
+ if (minYSet == 0xF) { // return 1 if all four are coincident
+ return coincident_line(cubic, reduction);
+ }
+ return vertical_line(cubic, reduction);
+ }
+ if (minYSet == 0xF) { // test for horizontal line
+ return horizontal_line(cubic, reduction);
+ }
+ int result = check_linear(cubic, reduction, minX, maxX, minY, maxY);
+ if (result) {
+ return result;
+ }
+ if (allowQuadratics && (result = check_quadratic(cubic, reduction))) {
+ return result;
+ }
+ memcpy(reduction, cubic, sizeof(Cubic));
+ return 4;
+}
diff --git a/experimental/Intersection/CubicReduceOrder_Test.cpp b/experimental/Intersection/CubicReduceOrder_Test.cpp
new file mode 100644
index 0000000000..29811568f6
--- /dev/null
+++ b/experimental/Intersection/CubicReduceOrder_Test.cpp
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "CurveIntersection.h"
+#include "CubicIntersection_TestData.h"
+#include "Intersection_Tests.h"
+#include "QuadraticIntersection_TestData.h"
+#include "TestUtilities.h"
+
+void CubicReduceOrder_Test() {
+ size_t index;
+ Cubic reduce;
+ int order;
+ enum {
+ RunAll,
+ RunPointDegenerates,
+ RunNotPointDegenerates,
+ RunLines,
+ RunNotLines,
+ RunModEpsilonLines,
+ RunLessEpsilonLines,
+ RunNegEpsilonLines,
+ RunQuadraticLines,
+ RunQuadraticModLines,
+ RunComputedLines,
+ RunNone
+ } run = RunAll;
+ int firstTestIndex = 0;
+#if 0
+ run = RunComputedLines;
+ firstTestIndex = 18;
+#endif
+ int firstPointDegeneratesTest = run == RunAll ? 0 : run == RunPointDegenerates ? firstTestIndex : INT_MAX;
+ int firstNotPointDegeneratesTest = run == RunAll ? 0 : run == RunNotPointDegenerates ? firstTestIndex : INT_MAX;
+ int firstLinesTest = run == RunAll ? 0 : run == RunLines ? firstTestIndex : INT_MAX;
+ int firstNotLinesTest = run == RunAll ? 0 : run == RunNotLines ? firstTestIndex : INT_MAX;
+ int firstModEpsilonTest = run == RunAll ? 0 : run == RunModEpsilonLines ? firstTestIndex : INT_MAX;
+ int firstLessEpsilonTest = run == RunAll ? 0 : run == RunLessEpsilonLines ? firstTestIndex : INT_MAX;
+ int firstNegEpsilonTest = run == RunAll ? 0 : run == RunNegEpsilonLines ? firstTestIndex : INT_MAX;
+ int firstQuadraticLineTest = run == RunAll ? 0 : run == RunQuadraticLines ? firstTestIndex : INT_MAX;
+ int firstQuadraticModLineTest = run == RunAll ? 0 : run == RunQuadraticModLines ? firstTestIndex : INT_MAX;
+ int firstComputedLinesTest = run == RunAll ? 0 : run == RunComputedLines ? firstTestIndex : INT_MAX;
+
+ for (index = firstPointDegeneratesTest; index < pointDegenerates_count; ++index) {
+ const Cubic& cubic = pointDegenerates[index];
+ order = reduceOrder(cubic, reduce, kReduceOrder_QuadraticsAllowed);
+ if (order != 1) {
+ printf("[%d] pointDegenerates order=%d\n", (int) index, order);
+ }
+ }
+ for (index = firstNotPointDegeneratesTest; index < notPointDegenerates_count; ++index) {
+ const Cubic& cubic = notPointDegenerates[index];
+ order = reduceOrder(cubic, reduce, kReduceOrder_QuadraticsAllowed);
+ if (order == 1) {
+ printf("[%d] notPointDegenerates order=%d\n", (int) index, order);
+ }
+ }
+ for (index = firstLinesTest; index < lines_count; ++index) {
+ const Cubic& cubic = lines[index];
+ order = reduceOrder(cubic, reduce, kReduceOrder_QuadraticsAllowed);
+ if (order != 2) {
+ printf("[%d] lines order=%d\n", (int) index, order);
+ }
+ }
+ for (index = firstNotLinesTest; index < notLines_count; ++index) {
+ const Cubic& cubic = notLines[index];
+ order = reduceOrder(cubic, reduce, kReduceOrder_QuadraticsAllowed);
+ if (order == 2) {
+ printf("[%d] notLines order=%d\n", (int) index, order);
+ }
+ }
+ for (index = firstModEpsilonTest; index < modEpsilonLines_count; ++index) {
+ const Cubic& cubic = modEpsilonLines[index];
+ order = reduceOrder(cubic, reduce, kReduceOrder_QuadraticsAllowed);
+ if (order == 2) {
+ printf("[%d] line mod by epsilon order=%d\n", (int) index, order);
+ }
+ }
+ for (index = firstLessEpsilonTest; index < lessEpsilonLines_count; ++index) {
+ const Cubic& cubic = lessEpsilonLines[index];
+ order = reduceOrder(cubic, reduce, kReduceOrder_QuadraticsAllowed);
+ if (order != 2) {
+ printf("[%d] line less by epsilon/2 order=%d\n", (int) index, order);
+ }
+ }
+ for (index = firstNegEpsilonTest; index < negEpsilonLines_count; ++index) {
+ const Cubic& cubic = negEpsilonLines[index];
+ order = reduceOrder(cubic, reduce, kReduceOrder_QuadraticsAllowed);
+ if (order != 2) {
+ printf("[%d] line neg by epsilon/2 order=%d\n", (int) index, order);
+ }
+ }
+ for (index = firstQuadraticLineTest; index < quadraticLines_count; ++index) {
+ const Quadratic& quad = quadraticLines[index];
+ Cubic cubic;
+ quad_to_cubic(quad, cubic);
+ order = reduceOrder(cubic, reduce, kReduceOrder_QuadraticsAllowed);
+ if (order != 2) {
+ printf("[%d] line quad order=%d\n", (int) index, order);
+ }
+ }
+ for (index = firstQuadraticModLineTest; index < quadraticModEpsilonLines_count; ++index) {
+ const Quadratic& quad = quadraticModEpsilonLines[index];
+ Cubic cubic;
+ quad_to_cubic(quad, cubic);
+ order = reduceOrder(cubic, reduce, kReduceOrder_QuadraticsAllowed);
+ if (order != 3) {
+ printf("[%d] line mod quad order=%d\n", (int) index, order);
+ }
+ }
+
+ // test if computed line end points are valid
+ for (index = firstComputedLinesTest; index < lines_count; ++index) {
+ const Cubic& cubic = lines[index];
+ bool controlsInside = controls_inside(cubic);
+ order = reduceOrder(cubic, reduce, kReduceOrder_QuadraticsAllowed);
+ if (reduce[0].x == reduce[1].x && reduce[0].y == reduce[1].y) {
+ printf("[%d] line computed ends match order=%d\n", (int) index, order);
+ }
+ if (controlsInside) {
+ if ( (reduce[0].x != cubic[0].x && reduce[0].x != cubic[3].x)
+ || (reduce[0].y != cubic[0].y && reduce[0].y != cubic[3].y)
+ || (reduce[1].x != cubic[0].x && reduce[1].x != cubic[3].x)
+ || (reduce[1].y != cubic[0].y && reduce[1].y != cubic[3].y)) {
+ printf("[%d] line computed ends order=%d\n", (int) index, order);
+ }
+ } else {
+ // binary search for extrema, compare against actual results
+ // while a control point is outside of bounding box formed by end points, split
+ _Rect bounds = {DBL_MAX, DBL_MAX, -DBL_MAX, -DBL_MAX};
+ find_tight_bounds(cubic, bounds);
+ if ( (!approximately_equal(reduce[0].x, bounds.left) && !approximately_equal(reduce[0].x, bounds.right))
+ || (!approximately_equal(reduce[0].y, bounds.top) && !approximately_equal(reduce[0].y, bounds.bottom))
+ || (!approximately_equal(reduce[1].x, bounds.left) && !approximately_equal(reduce[1].x, bounds.right))
+ || (!approximately_equal(reduce[1].y, bounds.top) && !approximately_equal(reduce[1].y, bounds.bottom))) {
+ printf("[%d] line computed tight bounds order=%d\n", (int) index, order);
+ }
+
+ }
+ }
+}
diff --git a/experimental/Intersection/DataTypes.cpp b/experimental/Intersection/DataTypes.cpp
new file mode 100644
index 0000000000..2832ab2c85
--- /dev/null
+++ b/experimental/Intersection/DataTypes.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "DataTypes.h"
+
+#include <sys/types.h>
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void *memcpy(void *, const void *, size_t);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#if USE_EPSILON
+const double PointEpsilon = 0.000001;
+const double SquaredEpsilon = PointEpsilon * PointEpsilon;
+#endif
+
+const int UlpsEpsilon = 16;
+
+// from http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
+union Float_t
+{
+ Float_t(float num = 0.0f) : f(num) {}
+ // Portable extraction of components.
+ bool Negative() const { return (i >> 31) != 0; }
+ int32_t RawMantissa() const { return i & ((1 << 23) - 1); }
+ int32_t RawExponent() const { return (i >> 23) & 0xFF; }
+
+ int32_t i;
+ float f;
+#ifdef _DEBUG
+ struct
+ { // Bitfields for exploration. Do not use in production code.
+ uint32_t mantissa : 23;
+ uint32_t exponent : 8;
+ uint32_t sign : 1;
+ } parts;
+#endif
+};
+
+bool AlmostEqualUlps(float A, float B)
+{
+ Float_t uA(A);
+ Float_t uB(B);
+
+ // Different signs means they do not match.
+ if (uA.Negative() != uB.Negative())
+ {
+ // Check for equality to make sure +0==-0
+ return A == B;
+ }
+
+ // Find the difference in ULPs.
+ int ulpsDiff = abs(uA.i - uB.i);
+ return ulpsDiff <= UlpsEpsilon;
+}
+
+// FIXME: obsolete, delete
+#if 1
+int UlpsDiff(float A, float B)
+{
+ Float_t uA(A);
+ Float_t uB(B);
+
+ return abs(uA.i - uB.i);
+}
+
+int FloatAsInt(float A)
+{
+ Float_t uA(A);
+ return uA.i;
+}
+#endif
+
diff --git a/experimental/Intersection/DataTypes.h b/experimental/Intersection/DataTypes.h
new file mode 100644
index 0000000000..33d88fa007
--- /dev/null
+++ b/experimental/Intersection/DataTypes.h
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef __DataTypes_h__
+#define __DataTypes_h__
+
+#include <assert.h>
+#include <float.h>
+#include <limits.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <sys/types.h>
+
+extern bool AlmostEqualUlps(float A, float B);
+// FIXME: delete
+int UlpsDiff(float A, float B);
+int FloatAsInt(float A);
+
+#if defined(IN_TEST)
+// FIXME: move to test-only header
+const double PointEpsilon = 0.000001;
+const double SquaredEpsilon = PointEpsilon * PointEpsilon;
+#endif
+
+inline bool approximately_zero(double x) {
+
+ return fabs(x) < FLT_EPSILON;
+}
+
+inline bool precisely_zero(double x) {
+
+ return fabs(x) < DBL_EPSILON;
+}
+
+inline bool approximately_zero(float x) {
+
+ return fabs(x) < FLT_EPSILON;
+}
+
+inline bool approximately_zero_squared(double x) {
+ return fabs(x) < FLT_EPSILON * FLT_EPSILON;
+}
+
+inline bool approximately_equal(double x, double y) {
+ return approximately_zero(x - y);
+}
+
+inline bool approximately_equal_squared(double x, double y) {
+ return approximately_equal(x, y);
+}
+
+inline bool approximately_greater(double x, double y) {
+ return approximately_equal(x, y) ? false : x > y;
+}
+
+inline bool approximately_lesser(double x, double y) {
+ return approximately_equal(x, y) ? false : x < y;
+}
+
+inline double approximately_pin(double x) {
+ return approximately_zero(x) ? 0 : x;
+}
+
+inline float approximately_pin(float x) {
+ return approximately_zero(x) ? 0 : x;
+}
+
+inline bool approximately_greater_than_one(double x) {
+ return x > 1 - FLT_EPSILON;
+}
+
+inline bool precisely_greater_than_one(double x) {
+ return x > 1 - DBL_EPSILON;
+}
+
+inline bool approximately_less_than_zero(double x) {
+ return x < FLT_EPSILON;
+}
+
+inline bool precisely_less_than_zero(double x) {
+ return x < DBL_EPSILON;
+}
+
+inline bool approximately_negative(double x) {
+ return x < FLT_EPSILON;
+}
+
+inline bool precisely_negative(double x) {
+ return x < DBL_EPSILON;
+}
+
+inline bool approximately_one_or_less(double x) {
+ return x < 1 + FLT_EPSILON;
+}
+
+inline bool approximately_positive(double x) {
+ return x > -FLT_EPSILON;
+}
+
+inline bool approximately_positive_squared(double x) {
+ return x > -(FLT_EPSILON * FLT_EPSILON);
+}
+
+inline bool approximately_zero_or_more(double x) {
+ return x > -FLT_EPSILON;
+}
+
+inline bool approximately_between(double a, double b, double c) {
+ assert(a <= c);
+ return a <= c ? approximately_negative(a - b) && approximately_negative(b - c)
+ : approximately_negative(b - a) && approximately_negative(c - b);
+}
+
+// returns true if (a <= b <= c) || (a >= b >= c)
+inline bool between(double a, double b, double c) {
+ assert(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0));
+ return (a - b) * (c - b) <= 0;
+}
+
+struct _Point {
+ double x;
+ double y;
+
+ void operator-=(const _Point& v) {
+ x -= v.x;
+ y -= v.y;
+ }
+
+ friend bool operator==(const _Point& a, const _Point& b) {
+ return a.x == b.x && a.y == b.y;
+ }
+
+ friend bool operator!=(const _Point& a, const _Point& b) {
+ return a.x!= b.x || a.y != b.y;
+ }
+
+ // note: this can not be implemented with
+ // return approximately_equal(a.y, y) && approximately_equal(a.x, x);
+ // because that will not take the magnitude of the values
+ bool approximatelyEqual(const _Point& a) const {
+ return AlmostEqualUlps((float) x, (float) a.x)
+ && AlmostEqualUlps((float) y, (float) a.y);
+ }
+
+};
+
+typedef _Point _Line[2];
+typedef _Point Quadratic[3];
+typedef _Point Cubic[4];
+
+struct _Rect {
+ double left;
+ double top;
+ double right;
+ double bottom;
+
+ void add(const _Point& pt) {
+ if (left > pt.x) {
+ left = pt.x;
+ }
+ if (top > pt.y) {
+ top = pt.y;
+ }
+ if (right < pt.x) {
+ right = pt.x;
+ }
+ if (bottom < pt.y) {
+ bottom = pt.y;
+ }
+ }
+
+ // FIXME: used by debugging only ?
+ bool contains(const _Point& pt) {
+ return approximately_between(left, pt.x, right)
+ && approximately_between(top, pt.y, bottom);
+ }
+
+ void set(const _Point& pt) {
+ left = right = pt.x;
+ top = bottom = pt.y;
+ }
+
+ void setBounds(const _Line& line) {
+ set(line[0]);
+ add(line[1]);
+ }
+
+ void setBounds(const Cubic& );
+ void setBounds(const Quadratic& );
+ void setRawBounds(const Cubic& );
+ void setRawBounds(const Quadratic& );
+};
+
+struct CubicPair {
+ const Cubic& first() const { return (const Cubic&) pts[0]; }
+ const Cubic& second() const { return (const Cubic&) pts[3]; }
+ _Point pts[7];
+};
+
+struct QuadraticPair {
+ const Quadratic& first() const { return (const Quadratic&) pts[0]; }
+ const Quadratic& second() const { return (const Quadratic&) pts[2]; }
+ _Point pts[5];
+};
+
+#endif // __DataTypes_h__
diff --git a/experimental/Intersection/EdgeDemo.cpp b/experimental/Intersection/EdgeDemo.cpp
new file mode 100644
index 0000000000..841678aa49
--- /dev/null
+++ b/experimental/Intersection/EdgeDemo.cpp
@@ -0,0 +1,343 @@
+#include "EdgeDemo.h"
+#include "EdgeWalker_Test.h"
+#include "ShapeOps.h"
+#import "SkCanvas.h"
+#import "SkPaint.h"
+
+extern void showPath(const SkPath& path, const char* str);
+
+static bool drawPaths(SkCanvas* canvas, const SkPath& path, bool useOld)
+{
+ SkPath out;
+#define SHOW_PATH 0
+#if SHOW_PATH
+ showPath(path, "original:");
+#endif
+ if (useOld) {
+ simplify(path, true, out);
+ } else {
+ simplifyx(path, out);
+ }
+#if SHOW_PATH
+ showPath(out, "simplified:");
+#endif
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setStyle(SkPaint::kStroke_Style);
+// paint.setStrokeWidth(6);
+ // paint.setColor(0x1F003f7f);
+ // canvas->drawPath(path, paint);
+ paint.setColor(0xFF305F00);
+ paint.setStrokeWidth(1);
+ canvas->drawPath(out, paint);
+ return true;
+}
+
+// Three circles bounce inside a rectangle. The circles describe three, four
+// or five points which in turn describe a polygon. The polygon points
+// bounce inside the circles. The circles rotate and scale over time. The
+// polygons are combined into a single path, simplified, and stroked.
+static bool drawCircles(SkCanvas* canvas, int step, bool useOld)
+{
+ const int circles = 3;
+ int scales[circles];
+ int angles[circles];
+ int locs[circles * 2];
+ int pts[circles * 2 * 4];
+ int c, p;
+ for (c = 0; c < circles; ++c) {
+ scales[c] = abs(10 - (step + c * 4) % 21);
+ angles[c] = (step + c * 6) % 600;
+ locs[c * 2] = abs(130 - (step + c * 9) % 261);
+ locs[c * 2 + 1] = abs(170 - (step + c * 11) % 341);
+ for (p = 0; p < 4; ++p) {
+ pts[c * 8 + p * 2] = abs(90 - ((step + c * 121 + p * 13) % 190));
+ pts[c * 8 + p * 2 + 1] = abs(110 - ((step + c * 223 + p * 17) % 230));
+ }
+ }
+ SkPath path;
+ for (c = 0; c < circles; ++c) {
+ for (p = 0; p < 4; ++p) {
+ SkScalar x = pts[c * 8 + p * 2];
+ SkScalar y = pts[c * 8 + p * 2 + 1];
+ x *= 3 + scales[c] / 10.0f;
+ y *= 3 + scales[c] / 10.0f;
+ SkScalar angle = angles[c] * 3.1415f * 2 / 600;
+ SkScalar temp = (SkScalar) (x * cos(angle) - y * sin(angle));
+ y = (SkScalar) (x * sin(angle) + y * cos(angle));
+ x = temp;
+ x += locs[c * 2] * 200 / 130.0f;
+ y += locs[c * 2 + 1] * 200 / 170.0f;
+ x += 50;
+ // y += 200;
+ if (p == 0) {
+ path.moveTo(x, y);
+ } else {
+ path.lineTo(x, y);
+ }
+ }
+ path.close();
+ }
+ return drawPaths(canvas, path, useOld);
+}
+
+static void createStar(SkPath& path, SkScalar innerRadius, SkScalar outerRadius,
+ SkScalar startAngle, int points, SkPoint center) {
+ SkScalar angle = startAngle;
+ for (int index = 0; index < points * 2; ++index) {
+ SkScalar radius = index & 1 ? outerRadius : innerRadius;
+ SkScalar x = (SkScalar) (radius * cos(angle));
+ SkScalar y = (SkScalar) (radius * sin(angle));
+ x += center.fX;
+ y += center.fY;
+ if (index == 0) {
+ path.moveTo(x, y);
+ } else {
+ path.lineTo(x, y);
+ }
+ angle += 3.1415f / points;
+ }
+ path.close();
+}
+
+static bool drawStars(SkCanvas* canvas, int step, bool useOld)
+{
+ SkPath path;
+ const int stars = 25;
+ int pts[stars];
+ // static bool initialize = true;
+ int s;
+ for (s = 0; s < stars; ++s) {
+ pts[s] = 4 + (s % 7);
+ }
+ SkPoint locs[stars];
+ SkScalar angles[stars];
+ SkScalar innerRadius[stars];
+ SkScalar outerRadius[stars];
+ const int width = 640;
+ const int height = 480;
+ const int margin = 30;
+ const int minRadius = 120;
+ const int maxInner = 800;
+ const int maxOuter = 1153;
+ for (s = 0; s < stars; ++s) {
+ int starW = (int) (width - margin * 2 + (SkScalar) s * (stars - s) / stars);
+ locs[s].fX = (int) (step * (1.3f * (s + 1) / stars) + s * 121) % (starW * 2);
+ if (locs[s].fX > starW) {
+ locs[s].fX = starW * 2 - locs[s].fX;
+ }
+ locs[s].fX += margin;
+ int starH = (int) (height - margin * 2 + (SkScalar) s * s / stars);
+ locs[s].fY = (int) (step * (1.7f * (s + 1) / stars) + s * 183) % (starH * 2);
+ if (locs[s].fY > starH) {
+ locs[s].fY = starH * 2 - locs[s].fY;
+ }
+ locs[s].fY += margin;
+ angles[s] = ((step + s * 47) % (360 * 4)) * 3.1415f / 180 / 4;
+ innerRadius[s] = (step + s * 30) % (maxInner * 2);
+ if (innerRadius[s] > maxInner) {
+ innerRadius[s] = (maxInner * 2) - innerRadius[s];
+ }
+ innerRadius[s] = innerRadius[s] / 4 + minRadius;
+ outerRadius[s] = (step + s * 70) % (maxOuter * 2);
+ if (outerRadius[s] > maxOuter) {
+ outerRadius[s] = (maxOuter * 2) - outerRadius[s];
+ }
+ outerRadius[s] = outerRadius[s] / 4 + minRadius;
+ createStar(path, innerRadius[s] / 4.0f, outerRadius[s] / 4.0f,
+ angles[s], pts[s], locs[s]);
+ }
+ return drawPaths(canvas, path, useOld);
+}
+
+static void tryRoncoOnce(const SkPath& path, const SkRect& target, bool show) {
+ // capture everything in a desired rectangle
+ SkPath tiny;
+ bool closed = true;
+ SkPath::Iter iter(path, false);
+ SkPoint pts[4];
+ SkPath::Verb verb;
+ int count = 0;
+ SkPoint lastPt;
+ while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+ switch (verb) {
+ case SkPath::kMove_Verb:
+ count = 0;
+ break;
+ case SkPath::kLine_Verb:
+ count = 1;
+ break;
+ case SkPath::kQuad_Verb:
+ count = 2;
+ break;
+ case SkPath::kCubic_Verb:
+ count = 3;
+ break;
+ case SkPath::kClose_Verb:
+ if (!closed) {
+ tiny.close();
+ closed = true;
+ }
+ count = 0;
+ break;
+ default:
+ SkDEBUGFAIL("bad verb");
+ }
+ if (!count) {
+ continue;
+ }
+ SkRect bounds;
+ bounds.set(pts[0].fX, pts[0].fY, pts[0].fX, pts[0].fY);
+ for (int i = 1; i <= count; ++i) {
+ bounds.growToInclude(pts[i].fX + 0.1f, pts[i].fY + 0.1f);
+ }
+ if (!SkRect::Intersects(target, bounds)) {
+ continue;
+ }
+ if (closed) {
+ tiny.moveTo(pts[0].fX, pts[0].fY);
+ closed = false;
+ } else if (pts[0] != lastPt) {
+ tiny.lineTo(pts[0].fX, pts[0].fY);
+ }
+ switch (verb) {
+ case SkPath::kLine_Verb:
+ tiny.lineTo(pts[1].fX, pts[1].fY);
+ lastPt = pts[1];
+ break;
+ case SkPath::kQuad_Verb:
+ tiny.quadTo(pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
+ lastPt = pts[2];
+ break;
+ case SkPath::kCubic_Verb:
+ tiny.cubicTo(pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY, pts[3].fX, pts[3].fY);
+ lastPt = pts[3];
+ break;
+ default:
+ SkDEBUGFAIL("bad verb");
+ }
+ }
+ if (!closed) {
+ tiny.close();
+ }
+ if (show) {
+ showPath(tiny, NULL);
+ SkDebugf("simplified:\n");
+ }
+ testSimplifyx(tiny);
+}
+
+static void tryRonco(const SkPath& path) {
+ int divMax = 64;
+ int divMin = 1;
+ int xDivMin = 0;
+ int yDivMin = 0;
+ bool allYs = true;
+ bool allXs = true;
+ if (1) {
+ divMax = divMin = 64;
+ xDivMin = 11;
+ yDivMin = 0;
+ allXs = true;
+ allYs = true;
+ }
+ for (int divs = divMax; divs >= divMin; divs /= 2) {
+ SkDebugf("divs=%d\n",divs);
+ const SkRect& overall = path.getBounds();
+ SkScalar cellWidth = overall.width() / divs * 2;
+ SkScalar cellHeight = overall.height() / divs * 2;
+ SkRect target;
+ int xDivMax = divMax == divMin && !allXs ? xDivMin + 1 : divs;
+ int yDivMax = divMax == divMin && !allYs ? yDivMin + 1 : divs;
+ for (int xDiv = xDivMin; xDiv < xDivMax; ++xDiv) {
+ SkDebugf("xDiv=%d\n",xDiv);
+ for (int yDiv = yDivMin; yDiv < yDivMax; ++yDiv) {
+ SkDebugf("yDiv=%d\n",yDiv);
+ target.setXYWH(overall.fLeft + (overall.width() - cellWidth) * xDiv / divs,
+ overall.fTop + (overall.height() - cellHeight) * yDiv / divs,
+ cellWidth, cellHeight);
+ tryRoncoOnce(path, target, divMax == divMin);
+ }
+ }
+ }
+}
+
+static bool drawLetters(SkCanvas* canvas, int step, bool useOld)
+{
+ SkPath path;
+ const int width = 640;
+ const int height = 480;
+ const char testStr[] = "Merge";
+ const int testStrLen = sizeof(testStr) - 1;
+ SkPoint textPos[testStrLen];
+ SkScalar widths[testStrLen];
+ SkPaint paint;
+ paint.setTextSize(40);
+ paint.setAntiAlias(true);
+ paint.getTextWidths(testStr, testStrLen, widths, NULL);
+ SkScalar running = 0;
+ for (int x = 0; x < testStrLen; ++x) {
+ SkScalar width = widths[x];
+ widths[x] = running;
+ running += width;
+ }
+ SkScalar bias = (width - widths[testStrLen - 1]) / 2;
+ for (int x = 0; x < testStrLen; ++x) {
+ textPos[x].fX = bias + widths[x];
+ textPos[x].fY = height / 2;
+ }
+ paint.setTextSize(40 + step / 100.0f);
+#if 0
+ bool oneShot = false;
+ for (int mask = 0; mask < 1 << testStrLen; ++mask) {
+ char maskStr[testStrLen];
+#if 1
+ mask = 12;
+ oneShot = true;
+#endif
+ SkDebugf("mask=%d\n", mask);
+ for (int letter = 0; letter < testStrLen; ++letter) {
+ maskStr[letter] = mask & (1 << letter) ? testStr[letter] : ' ';
+ }
+ paint.getPosTextPath(maskStr, testStrLen, textPos, &path);
+ // showPath(path, NULL);
+ // SkDebugf("%d simplified:\n", mask);
+ tryRonco(path);
+ // testSimplifyx(path);
+ if (oneShot) {
+ break;
+ }
+ }
+#endif
+ paint.getPosTextPath(testStr, testStrLen, textPos, &path);
+#if 0
+ tryRonco(path);
+ SkDebugf("RoncoDone!\n");
+#endif
+#if 0
+ showPath(path, NULL);
+ SkDebugf("simplified:\n");
+#endif
+ return drawPaths(canvas, path, false);
+}
+
+static bool (*drawDemos[])(SkCanvas* , int , bool ) = {
+ drawStars,
+ drawCircles,
+ drawLetters,
+};
+
+static size_t drawDemosCount = sizeof(drawDemos) / sizeof(drawDemos[0]);
+
+static bool (*firstTest)(SkCanvas* , int , bool) = drawLetters;
+
+
+bool DrawEdgeDemo(SkCanvas* canvas, int step, bool useOld) {
+ size_t index = 0;
+ if (firstTest) {
+ while (index < drawDemosCount && drawDemos[index] != firstTest) {
+ ++index;
+ }
+ }
+ return (*drawDemos[index])(canvas, step, useOld);
+}
diff --git a/experimental/Intersection/EdgeDemoApp.mm b/experimental/Intersection/EdgeDemoApp.mm
new file mode 100644
index 0000000000..7c50fea617
--- /dev/null
+++ b/experimental/Intersection/EdgeDemoApp.mm
@@ -0,0 +1,94 @@
+#include "EdgeDemo.h"
+#import "SkCanvas.h"
+#import "SkWindow.h"
+#include "SkGraphics.h"
+#include "SkCGUtils.h"
+
+#include <time.h>
+#include <sys/time.h>
+
+class SkSampleView : public SkView {
+public:
+ SkSampleView() {
+ this->setVisibleP(true);
+ this->setClipToBounds(false);
+ useOld = false;
+ };
+protected:
+ virtual void onDraw(SkCanvas* canvas) {
+ static int step = 0; // 17907 drawLetters first error
+ // drawStars triggers error at 33348
+ // drawStars error not easy to debug last time I checked
+ static double seconds;
+ if (step == -1) {
+ timeval t;
+ gettimeofday(&t, NULL);
+ seconds = t.tv_sec+t.tv_usec/1000000.0;
+ step = 0;
+ }
+ canvas->drawColor(SK_ColorWHITE);
+ if (DrawEdgeDemo(canvas, step, useOld)) {
+ ++step;
+ if (step == -1) {
+ timeval t;
+ gettimeofday(&t, NULL);
+ double last = seconds;
+ seconds = t.tv_sec+t.tv_usec/1000000.0;
+ SkDebugf("old=%d seconds=%g\n", useOld, seconds - last);
+ useOld ^= true;
+ step = 0;
+ }
+ inval(NULL);
+ }
+ }
+
+ virtual Click* onFindClickHandler(SkScalar , SkScalar ) {
+ useOld ^= true;
+ return NULL;
+ }
+
+private:
+ bool useOld;
+ typedef SkView INHERITED;
+};
+
+void application_init();
+void application_term();
+
+void application_init() {
+ SkGraphics::Init();
+ SkEvent::Init();
+}
+
+void application_term() {
+ SkGraphics::Term();
+ SkEvent::Term();
+}
+
+class FillLayout : public SkView::Layout {
+protected:
+ virtual void onLayoutChildren(SkView* parent) {
+ SkView* view = SkView::F2BIter(parent).next();
+ view->setSize(parent->width(), parent->height());
+ }
+};
+
+#import "SimpleApp.h"
+
+@implementation SimpleNSView
+
+- (id)initWithDefaults {
+ if ((self = [super initWithDefaults])) {
+ fWind = new SkOSWindow(self);
+ fWind->setLayout(new FillLayout, false);
+ fWind->attachChildToFront(new SkSampleView)->unref();
+ }
+ return self;
+}
+
+- (void)drawRect:(NSRect)dirtyRect {
+ CGContextRef ctx = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+ SkCGDrawBitmap(ctx, fWind->getBitmap(), 0, 0);
+}
+
+@end
diff --git a/experimental/Intersection/EdgeWalker.cpp b/experimental/Intersection/EdgeWalker.cpp
new file mode 100644
index 0000000000..12fe30da79
--- /dev/null
+++ b/experimental/Intersection/EdgeWalker.cpp
@@ -0,0 +1,2705 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "Simplify.h"
+
+#undef SkASSERT
+#define SkASSERT(cond) while (!(cond)) { sk_throw(); }
+
+// FIXME: remove once debugging is complete
+#if 01 // set to 1 for no debugging whatsoever
+
+//const bool gRunTestsInOneThread = false;
+
+#define DEBUG_ACTIVE_LESS_THAN 0
+#define DEBUG_ADD 0
+#define DEBUG_ADD_BOTTOM_TS 0
+#define DEBUG_ADD_INTERSECTING_TS 0
+#define DEBUG_ADJUST_COINCIDENT 0
+#define DEBUG_ASSEMBLE 0
+#define DEBUG_BOTTOM 0
+#define DEBUG_BRIDGE 0
+#define DEBUG_DUMP 0
+#define DEBUG_SORT_HORIZONTAL 0
+#define DEBUG_OUT 0
+#define DEBUG_OUT_LESS_THAN 0
+#define DEBUG_SPLIT 0
+#define DEBUG_STITCH_EDGE 0
+#define DEBUG_TRIM_LINE 0
+
+#else
+
+//const bool gRunTestsInOneThread = true;
+
+#define DEBUG_ACTIVE_LESS_THAN 0
+#define DEBUG_ADD 01
+#define DEBUG_ADD_BOTTOM_TS 0
+#define DEBUG_ADD_INTERSECTING_TS 0
+#define DEBUG_ADJUST_COINCIDENT 1
+#define DEBUG_ASSEMBLE 1
+#define DEBUG_BOTTOM 0
+#define DEBUG_BRIDGE 1
+#define DEBUG_DUMP 1
+#define DEBUG_SORT_HORIZONTAL 01
+#define DEBUG_OUT 01
+#define DEBUG_OUT_LESS_THAN 0
+#define DEBUG_SPLIT 1
+#define DEBUG_STITCH_EDGE 1
+#define DEBUG_TRIM_LINE 1
+
+#endif
+
+#if DEBUG_ASSEMBLE || DEBUG_BRIDGE
+static const char* kLVerbStr[] = {"", "line", "quad", "cubic"};
+#endif
+#if DEBUG_STITCH_EDGE
+static const char* kUVerbStr[] = {"", "Line", "Quad", "Cubic"};
+#endif
+
+static int LineIntersect(const SkPoint a[2], const SkPoint b[2],
+ Intersections& intersections) {
+ const _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
+ const _Line bLine = {{b[0].fX, b[0].fY}, {b[1].fX, b[1].fY}};
+ return intersect(aLine, bLine, intersections.fT[0], intersections.fT[1]);
+}
+
+static int QuadLineIntersect(const SkPoint a[3], const SkPoint b[2],
+ Intersections& intersections) {
+ const Quadratic aQuad = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY}};
+ const _Line bLine = {{b[0].fX, b[0].fY}, {b[1].fX, b[1].fY}};
+ intersect(aQuad, bLine, intersections);
+ return intersections.fUsed;
+}
+
+static int CubicLineIntersect(const SkPoint a[2], const SkPoint b[3],
+ Intersections& intersections) {
+ const Cubic aCubic = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY},
+ {a[3].fX, a[3].fY}};
+ const _Line bLine = {{b[0].fX, b[0].fY}, {b[1].fX, b[1].fY}};
+ return intersect(aCubic, bLine, intersections.fT[0], intersections.fT[1]);
+}
+
+static int QuadIntersect(const SkPoint a[3], const SkPoint b[3],
+ Intersections& intersections) {
+ const Quadratic aQuad = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY}};
+ const Quadratic bQuad = {{b[0].fX, b[0].fY}, {b[1].fX, b[1].fY}, {b[2].fX, b[2].fY}};
+ intersect(aQuad, bQuad, intersections);
+ return intersections.fUsed;
+}
+
+static int CubicIntersect(const SkPoint a[4], const SkPoint b[4],
+ Intersections& intersections) {
+ const Cubic aCubic = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY},
+ {a[3].fX, a[3].fY}};
+ const Cubic bCubic = {{b[0].fX, b[0].fY}, {b[1].fX, b[1].fY}, {b[2].fX, b[2].fY},
+ {b[3].fX, b[3].fY}};
+ intersect(aCubic, bCubic, intersections);
+ return intersections.fUsed;
+}
+
+static int LineIntersect(const SkPoint a[2], SkScalar left, SkScalar right,
+ SkScalar y, double aRange[2]) {
+ const _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
+ return horizontalLineIntersect(aLine, left, right, y, aRange);
+}
+
+static int QuadIntersect(const SkPoint a[3], SkScalar left, SkScalar right,
+ SkScalar y, double aRange[3]) {
+ const Quadratic aQuad = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY}};
+ return horizontalIntersect(aQuad, left, right, y, aRange);
+}
+
+static int CubicIntersect(const SkPoint a[4], SkScalar left, SkScalar right,
+ SkScalar y, double aRange[4]) {
+ const Cubic aCubic = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY},
+ {a[3].fX, a[3].fY}};
+ return horizontalIntersect(aCubic, left, right, y, aRange);
+}
+
+static void LineXYAtT(const SkPoint a[2], double t, SkPoint* out) {
+ const _Line line = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
+ double x, y;
+ xy_at_t(line, t, x, y);
+ out->fX = SkDoubleToScalar(x);
+ out->fY = SkDoubleToScalar(y);
+}
+
+static void QuadXYAtT(const SkPoint a[3], double t, SkPoint* out) {
+ const Quadratic quad = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY}};
+ double x, y;
+ xy_at_t(quad, t, x, y);
+ out->fX = SkDoubleToScalar(x);
+ out->fY = SkDoubleToScalar(y);
+}
+
+static void CubicXYAtT(const SkPoint a[4], double t, SkPoint* out) {
+ const Cubic cubic = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY},
+ {a[3].fX, a[3].fY}};
+ double x, y;
+ xy_at_t(cubic, t, x, y);
+ out->fX = SkDoubleToScalar(x);
+ out->fY = SkDoubleToScalar(y);
+}
+
+static SkScalar LineYAtT(const SkPoint a[2], double t) {
+ const _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
+ double y;
+ xy_at_t(aLine, t, *(double*) 0, y);
+ return SkDoubleToScalar(y);
+}
+
+static SkScalar QuadYAtT(const SkPoint a[3], double t) {
+ const Quadratic quad = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY}};
+ double y;
+ xy_at_t(quad, t, *(double*) 0, y);
+ return SkDoubleToScalar(y);
+}
+
+static SkScalar CubicYAtT(const SkPoint a[4], double t) {
+ const Cubic cubic = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY},
+ {a[3].fX, a[3].fY}};
+ double y;
+ xy_at_t(cubic, t, *(double*) 0, y);
+ return SkDoubleToScalar(y);
+}
+
+static void LineSubDivide(const SkPoint a[2], double startT, double endT,
+ SkPoint sub[2]) {
+ const _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
+ _Line dst;
+ sub_divide(aLine, startT, endT, dst);
+ sub[0].fX = SkDoubleToScalar(dst[0].x);
+ sub[0].fY = SkDoubleToScalar(dst[0].y);
+ sub[1].fX = SkDoubleToScalar(dst[1].x);
+ sub[1].fY = SkDoubleToScalar(dst[1].y);
+}
+
+static void QuadSubDivide(const SkPoint a[3], double startT, double endT,
+ SkPoint sub[3]) {
+ const Quadratic aQuad = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY},
+ {a[2].fX, a[2].fY}};
+ Quadratic dst;
+ sub_divide(aQuad, startT, endT, dst);
+ sub[0].fX = SkDoubleToScalar(dst[0].x);
+ sub[0].fY = SkDoubleToScalar(dst[0].y);
+ sub[1].fX = SkDoubleToScalar(dst[1].x);
+ sub[1].fY = SkDoubleToScalar(dst[1].y);
+ sub[2].fX = SkDoubleToScalar(dst[2].x);
+ sub[2].fY = SkDoubleToScalar(dst[2].y);
+}
+
+static void CubicSubDivide(const SkPoint a[4], double startT, double endT,
+ SkPoint sub[4]) {
+ const Cubic aCubic = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY},
+ {a[2].fX, a[2].fY}, {a[3].fX, a[3].fY}};
+ Cubic dst;
+ sub_divide(aCubic, startT, endT, dst);
+ sub[0].fX = SkDoubleToScalar(dst[0].x);
+ sub[0].fY = SkDoubleToScalar(dst[0].y);
+ sub[1].fX = SkDoubleToScalar(dst[1].x);
+ sub[1].fY = SkDoubleToScalar(dst[1].y);
+ sub[2].fX = SkDoubleToScalar(dst[2].x);
+ sub[2].fY = SkDoubleToScalar(dst[2].y);
+ sub[3].fX = SkDoubleToScalar(dst[3].x);
+ sub[3].fY = SkDoubleToScalar(dst[3].y);
+}
+
+static void QuadSubBounds(const SkPoint a[3], double startT, double endT,
+ SkRect& bounds) {
+ SkPoint dst[3];
+ QuadSubDivide(a, startT, endT, dst);
+ bounds.fLeft = bounds.fRight = dst[0].fX;
+ bounds.fTop = bounds.fBottom = dst[0].fY;
+ for (int index = 1; index < 3; ++index) {
+ bounds.growToInclude(dst[index].fX, dst[index].fY);
+ }
+}
+
+static void CubicSubBounds(const SkPoint a[4], double startT, double endT,
+ SkRect& bounds) {
+ SkPoint dst[4];
+ CubicSubDivide(a, startT, endT, dst);
+ bounds.fLeft = bounds.fRight = dst[0].fX;
+ bounds.fTop = bounds.fBottom = dst[0].fY;
+ for (int index = 1; index < 4; ++index) {
+ bounds.growToInclude(dst[index].fX, dst[index].fY);
+ }
+}
+
+static SkPath::Verb QuadReduceOrder(SkPoint a[4]) {
+ const Quadratic aQuad = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY},
+ {a[2].fX, a[2].fY}};
+ Quadratic dst;
+ int order = reduceOrder(aQuad, dst);
+ for (int index = 0; index < order; ++index) {
+ a[index].fX = SkDoubleToScalar(dst[index].x);
+ a[index].fY = SkDoubleToScalar(dst[index].y);
+ }
+ if (order == 1) { // FIXME: allow returning points, caller should discard
+ a[1] = a[0];
+ return (SkPath::Verb) order;
+ }
+ return (SkPath::Verb) (order - 1);
+}
+
+static SkPath::Verb CubicReduceOrder(SkPoint a[4]) {
+ const Cubic aCubic = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY},
+ {a[2].fX, a[2].fY}, {a[3].fX, a[3].fY}};
+ Cubic dst;
+ int order = reduceOrder(aCubic, dst, kReduceOrder_QuadraticsAllowed);
+ for (int index = 0; index < order; ++index) {
+ a[index].fX = SkDoubleToScalar(dst[index].x);
+ a[index].fY = SkDoubleToScalar(dst[index].y);
+ }
+ if (order == 1) { // FIXME: allow returning points, caller should discard
+ a[1] = a[0];
+ return (SkPath::Verb) order;
+ }
+ return (SkPath::Verb) (order - 1);
+}
+
+static bool IsCoincident(const SkPoint a[2], const SkPoint& above,
+ const SkPoint& below) {
+ const _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
+ const _Line bLine = {{above.fX, above.fY}, {below.fX, below.fY}};
+ return implicit_matches_ulps(aLine, bLine, 32);
+}
+
+/*
+list of edges
+bounds for edge
+sort
+active T
+
+if a contour's bounds is outside of the active area, no need to create edges
+*/
+
+/* given one or more paths,
+ find the bounds of each contour, select the active contours
+ for each active contour, compute a set of edges
+ each edge corresponds to one or more lines and curves
+ leave edges unbroken as long as possible
+ when breaking edges, compute the t at the break but leave the control points alone
+
+ */
+
+void contourBounds(const SkPath& path, SkTDArray<SkRect>& boundsArray) {
+ SkPath::Iter iter(path, false);
+ SkPoint pts[4];
+ SkPath::Verb verb;
+ SkRect bounds;
+ bounds.setEmpty();
+ int count = 0;
+ while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+ switch (verb) {
+ case SkPath::kMove_Verb:
+ if (!bounds.isEmpty()) {
+ *boundsArray.append() = bounds;
+ }
+ bounds.set(pts[0].fX, pts[0].fY, pts[0].fX, pts[0].fY);
+ count = 0;
+ break;
+ case SkPath::kLine_Verb:
+ count = 1;
+ break;
+ case SkPath::kQuad_Verb:
+ count = 2;
+ break;
+ case SkPath::kCubic_Verb:
+ count = 3;
+ break;
+ case SkPath::kClose_Verb:
+ count = 0;
+ break;
+ default:
+ SkDEBUGFAIL("bad verb");
+ return;
+ }
+ for (int i = 1; i <= count; ++i) {
+ bounds.growToInclude(pts[i].fX, pts[i].fY);
+ }
+ }
+}
+
+static bool extendLine(const SkPoint line[2], const SkPoint& add) {
+ // FIXME: allow this to extend lines that have slopes that are nearly equal
+ SkScalar dx1 = line[1].fX - line[0].fX;
+ SkScalar dy1 = line[1].fY - line[0].fY;
+ SkScalar dx2 = add.fX - line[0].fX;
+ SkScalar dy2 = add.fY - line[0].fY;
+ return dx1 * dy2 == dx2 * dy1;
+}
+
+// OPTIMIZATION: this should point to a list of input data rather than duplicating
+// the line data here. This would reduce the need to assemble the results.
+struct OutEdge {
+ bool operator<(const OutEdge& rh) const {
+ const SkPoint& first = fPts[0];
+ const SkPoint& rhFirst = rh.fPts[0];
+ return first.fY == rhFirst.fY
+ ? first.fX < rhFirst.fX
+ : first.fY < rhFirst.fY;
+ }
+
+ SkPoint fPts[4];
+ int fID; // id of edge generating data
+ uint8_t fVerb; // FIXME: not read from everywhere
+ bool fCloseCall; // edge is trimmable if not originally coincident
+};
+
+class OutEdgeBuilder {
+public:
+ OutEdgeBuilder(bool fill)
+ : fFill(fill) {
+ }
+
+ void addCurve(const SkPoint line[4], SkPath::Verb verb, int id,
+ bool closeCall) {
+ OutEdge& newEdge = fEdges.push_back();
+ memcpy(newEdge.fPts, line, (verb + 1) * sizeof(SkPoint));
+ newEdge.fVerb = verb;
+ newEdge.fID = id;
+ newEdge.fCloseCall = closeCall;
+ }
+
+ bool trimLine(SkScalar y, int id) {
+ size_t count = fEdges.count();
+ while (count-- != 0) {
+ OutEdge& edge = fEdges[count];
+ if (edge.fID != id) {
+ continue;
+ }
+ if (edge.fCloseCall) {
+ return false;
+ }
+ SkASSERT(edge.fPts[0].fY <= y);
+ if (edge.fPts[1].fY <= y) {
+ continue;
+ }
+ edge.fPts[1].fX = edge.fPts[0].fX + (y - edge.fPts[0].fY)
+ * (edge.fPts[1].fX - edge.fPts[0].fX)
+ / (edge.fPts[1].fY - edge.fPts[0].fY);
+ edge.fPts[1].fY = y;
+#if DEBUG_TRIM_LINE
+ SkDebugf("%s edge=%d %1.9g,%1.9g\n", __FUNCTION__, id,
+ edge.fPts[1].fX, y);
+#endif
+ return true;
+ }
+ return false;
+ }
+
+ void assemble(SkPath& simple) {
+ size_t listCount = fEdges.count();
+ if (listCount == 0) {
+ return;
+ }
+ do {
+ size_t listIndex = 0;
+ int advance = 1;
+ while (listIndex < listCount && fTops[listIndex] == 0) {
+ ++listIndex;
+ }
+ if (listIndex >= listCount) {
+ break;
+ }
+ int closeEdgeIndex = -listIndex - 1;
+ // the curve is deferred and not added right away because the
+ // following edge may extend the first curve.
+ SkPoint firstPt, lastCurve[4];
+ uint8_t lastVerb;
+#if DEBUG_ASSEMBLE
+ int firstIndex, lastIndex;
+ const int tab = 8;
+#endif
+ bool doMove = true;
+ int edgeIndex;
+ do {
+ SkPoint* ptArray = fEdges[listIndex].fPts;
+ uint8_t verb = fEdges[listIndex].fVerb;
+ SkPoint* curve[4];
+ if (advance < 0) {
+ curve[0] = &ptArray[verb];
+ if (verb == SkPath::kCubic_Verb) {
+ curve[1] = &ptArray[2];
+ curve[2] = &ptArray[1];
+ }
+ curve[verb] = &ptArray[0];
+ } else {
+ curve[0] = &ptArray[0];
+ if (verb == SkPath::kCubic_Verb) {
+ curve[1] = &ptArray[1];
+ curve[2] = &ptArray[2];
+ }
+ curve[verb] = &ptArray[verb];
+ }
+ if (verb == SkPath::kQuad_Verb) {
+ curve[1] = &ptArray[1];
+ }
+ if (doMove) {
+ firstPt = *curve[0];
+ simple.moveTo(curve[0]->fX, curve[0]->fY);
+#if DEBUG_ASSEMBLE
+ SkDebugf("%s %d moveTo (%g,%g)\n", __FUNCTION__,
+ listIndex + 1, curve[0]->fX, curve[0]->fY);
+ firstIndex = listIndex;
+#endif
+ for (int index = 0; index <= verb; ++index) {
+ lastCurve[index] = *curve[index];
+ }
+ doMove = false;
+ } else {
+ bool gap = lastCurve[lastVerb] != *curve[0];
+ if (gap || lastVerb != SkPath::kLine_Verb) { // output the accumulated curve before the gap
+ // FIXME: see comment in bridge -- this probably
+ // conceals errors
+ SkASSERT(fFill && UlpsDiff(lastCurve[lastVerb].fY,
+ curve[0]->fY) <= 10);
+ switch (lastVerb) {
+ case SkPath::kLine_Verb:
+ simple.lineTo(lastCurve[1].fX, lastCurve[1].fY);
+ break;
+ case SkPath::kQuad_Verb:
+ simple.quadTo(lastCurve[1].fX, lastCurve[1].fY,
+ lastCurve[2].fX, lastCurve[2].fY);
+ break;
+ case SkPath::kCubic_Verb:
+ simple.cubicTo(lastCurve[1].fX, lastCurve[1].fY,
+ lastCurve[2].fX, lastCurve[2].fY,
+ lastCurve[3].fX, lastCurve[3].fY);
+ break;
+ }
+#if DEBUG_ASSEMBLE
+ SkDebugf("%*s %d %sTo (%g,%g)\n", tab, "", lastIndex + 1,
+ kLVerbStr[lastVerb], lastCurve[lastVerb].fX,
+ lastCurve[lastVerb].fY);
+#endif
+ }
+ int firstCopy = 1;
+ if (gap || (lastVerb == SkPath::kLine_Verb
+ && (verb != SkPath::kLine_Verb
+ || !extendLine(lastCurve, *curve[verb])))) {
+ // FIXME: see comment in bridge -- this probably
+ // conceals errors
+ SkASSERT(lastCurve[lastVerb] == *curve[0] ||
+ (fFill && UlpsDiff(lastCurve[lastVerb].fY,
+ curve[0]->fY) <= 10));
+ simple.lineTo(curve[0]->fX, curve[0]->fY);
+#if DEBUG_ASSEMBLE
+ SkDebugf("%*s %d gap lineTo (%g,%g)\n", tab, "",
+ lastIndex + 1, curve[0]->fX, curve[0]->fY);
+#endif
+ firstCopy = 0;
+ } else if (lastVerb != SkPath::kLine_Verb) {
+ firstCopy = 0;
+ }
+ for (int index = firstCopy; index <= verb; ++index) {
+ lastCurve[index] = *curve[index];
+ }
+ }
+ lastVerb = verb;
+#if DEBUG_ASSEMBLE
+ lastIndex = listIndex;
+#endif
+ if (advance < 0) {
+ edgeIndex = fTops[listIndex];
+ fTops[listIndex] = 0;
+ } else {
+ edgeIndex = fBottoms[listIndex];
+ fBottoms[listIndex] = 0;
+ }
+ if (edgeIndex) {
+ listIndex = abs(edgeIndex) - 1;
+ if (edgeIndex < 0) {
+ fTops[listIndex] = 0;
+ } else {
+ fBottoms[listIndex] = 0;
+ }
+ }
+ if (edgeIndex == closeEdgeIndex || edgeIndex == 0) {
+ switch (lastVerb) {
+ case SkPath::kLine_Verb:
+ simple.lineTo(lastCurve[1].fX, lastCurve[1].fY);
+ break;
+ case SkPath::kQuad_Verb:
+ simple.quadTo(lastCurve[1].fX, lastCurve[1].fY,
+ lastCurve[2].fX, lastCurve[2].fY);
+ break;
+ case SkPath::kCubic_Verb:
+ simple.cubicTo(lastCurve[1].fX, lastCurve[1].fY,
+ lastCurve[2].fX, lastCurve[2].fY,
+ lastCurve[3].fX, lastCurve[3].fY);
+ break;
+ }
+#if DEBUG_ASSEMBLE
+ SkDebugf("%*s %d %sTo last (%g, %g)\n", tab, "",
+ lastIndex + 1, kLVerbStr[lastVerb],
+ lastCurve[lastVerb].fX, lastCurve[lastVerb].fY);
+#endif
+ if (lastCurve[lastVerb] != firstPt) {
+ simple.lineTo(firstPt.fX, firstPt.fY);
+#if DEBUG_ASSEMBLE
+ SkDebugf("%*s %d final line (%g, %g)\n", tab, "",
+ firstIndex + 1, firstPt.fX, firstPt.fY);
+#endif
+ }
+ simple.close();
+#if DEBUG_ASSEMBLE
+ SkDebugf("%*s close\n", tab, "");
+#endif
+ break;
+ }
+ // if this and next edge go different directions
+#if DEBUG_ASSEMBLE
+ SkDebugf("%*s advance=%d edgeIndex=%d flip=%s\n", tab, "",
+ advance, edgeIndex, advance > 0 ^ edgeIndex < 0 ?
+ "true" : "false");
+#endif
+ if (advance > 0 ^ edgeIndex < 0) {
+ advance = -advance;
+ }
+ } while (edgeIndex);
+ } while (true);
+ }
+
+ // sort points by y, then x
+ // if x/y is identical, sort bottoms before tops
+ // if identical and both tops/bottoms, sort by angle
+ static bool lessThan(SkTArray<OutEdge>& edges, const int one,
+ const int two) {
+ const OutEdge& oneEdge = edges[abs(one) - 1];
+ int oneIndex = one < 0 ? 0 : oneEdge.fVerb;
+ const SkPoint& startPt1 = oneEdge.fPts[oneIndex];
+ const OutEdge& twoEdge = edges[abs(two) - 1];
+ int twoIndex = two < 0 ? 0 : twoEdge.fVerb;
+ const SkPoint& startPt2 = twoEdge.fPts[twoIndex];
+ if (startPt1.fY != startPt2.fY) {
+ #if DEBUG_OUT_LESS_THAN
+ SkDebugf("%s %d<%d (%g,%g) %s startPt1.fY < startPt2.fY\n", __FUNCTION__,
+ one, two, startPt1.fY, startPt2.fY,
+ startPt1.fY < startPt2.fY ? "true" : "false");
+ #endif
+ return startPt1.fY < startPt2.fY;
+ }
+ if (startPt1.fX != startPt2.fX) {
+ #if DEBUG_OUT_LESS_THAN
+ SkDebugf("%s %d<%d (%g,%g) %s startPt1.fX < startPt2.fX\n", __FUNCTION__,
+ one, two, startPt1.fX, startPt2.fX,
+ startPt1.fX < startPt2.fX ? "true" : "false");
+ #endif
+ return startPt1.fX < startPt2.fX;
+ }
+ const SkPoint& endPt1 = oneEdge.fPts[oneIndex ^ oneEdge.fVerb];
+ const SkPoint& endPt2 = twoEdge.fPts[twoIndex ^ twoEdge.fVerb];
+ SkScalar dy1 = startPt1.fY - endPt1.fY;
+ SkScalar dy2 = startPt2.fY - endPt2.fY;
+ SkScalar dy1y2 = dy1 * dy2;
+ if (dy1y2 < 0) { // different signs
+ #if DEBUG_OUT_LESS_THAN
+ SkDebugf("%s %d<%d %s dy1 > 0\n", __FUNCTION__, one, two,
+ dy1 > 0 ? "true" : "false");
+ #endif
+ return dy1 > 0; // one < two if one goes up and two goes down
+ }
+ if (dy1y2 == 0) {
+ #if DEBUG_OUT_LESS_THAN
+ SkDebugf("%s %d<%d %s endPt1.fX < endPt2.fX\n", __FUNCTION__,
+ one, two, endPt1.fX < endPt2.fX ? "true" : "false");
+ #endif
+ return endPt1.fX < endPt2.fX;
+ }
+ SkScalar dx1y2 = (startPt1.fX - endPt1.fX) * dy2;
+ SkScalar dx2y1 = (startPt2.fX - endPt2.fX) * dy1;
+ #if DEBUG_OUT_LESS_THAN
+ SkDebugf("%s %d<%d %s dy2 < 0 ^ dx1y2 < dx2y1\n", __FUNCTION__,
+ one, two, dy2 < 0 ^ dx1y2 < dx2y1 ? "true" : "false");
+ #endif
+ return dy2 > 0 ^ dx1y2 < dx2y1;
+ }
+
+ // Sort the indices of paired points and then create more indices so
+ // assemble() can find the next edge and connect the top or bottom
+ void bridge() {
+ size_t index;
+ size_t count = fEdges.count();
+ if (!count) {
+ return;
+ }
+ SkASSERT(!fFill || count > 1);
+ fTops.setCount(count);
+ sk_bzero(fTops.begin(), sizeof(fTops[0]) * count);
+ fBottoms.setCount(count);
+ sk_bzero(fBottoms.begin(), sizeof(fBottoms[0]) * count);
+ SkTDArray<int> order;
+ for (index = 1; index <= count; ++index) {
+ *order.append() = -index;
+ }
+ for (index = 1; index <= count; ++index) {
+ *order.append() = index;
+ }
+ QSort<SkTArray<OutEdge>, int>(fEdges, order.begin(), order.end() - 1, lessThan);
+ int* lastPtr = order.end() - 1;
+ int* leftPtr = order.begin();
+ while (leftPtr < lastPtr) {
+ int leftIndex = *leftPtr;
+ int leftOutIndex = abs(leftIndex) - 1;
+ const OutEdge& left = fEdges[leftOutIndex];
+ int* rightPtr = leftPtr + 1;
+ int rightIndex = *rightPtr;
+ int rightOutIndex = abs(rightIndex) - 1;
+ const OutEdge& right = fEdges[rightOutIndex];
+ bool pairUp = fFill;
+ if (!pairUp) {
+ const SkPoint& leftMatch =
+ left.fPts[leftIndex < 0 ? 0 : left.fVerb];
+ const SkPoint& rightMatch =
+ right.fPts[rightIndex < 0 ? 0 : right.fVerb];
+ pairUp = leftMatch == rightMatch;
+ } else {
+ #if DEBUG_OUT
+ // FIXME : not happy that error in low bit is allowed
+ // this probably conceals error elsewhere
+ if (UlpsDiff(left.fPts[leftIndex < 0 ? 0 : left.fVerb].fY,
+ right.fPts[rightIndex < 0 ? 0 : right.fVerb].fY) > 1) {
+ *fMismatches.append() = leftIndex;
+ if (rightPtr == lastPtr) {
+ *fMismatches.append() = rightIndex;
+ }
+ pairUp = false;
+ }
+ #else
+ SkASSERT(UlpsDiff(left.fPts[leftIndex < 0 ? 0 : left.fVerb].fY,
+ right.fPts[rightIndex < 0 ? 0 : right.fVerb].fY) <= 10);
+ #endif
+ }
+ if (pairUp) {
+ if (leftIndex < 0) {
+ fTops[leftOutIndex] = rightIndex;
+ } else {
+ fBottoms[leftOutIndex] = rightIndex;
+ }
+ if (rightIndex < 0) {
+ fTops[rightOutIndex] = leftIndex;
+ } else {
+ fBottoms[rightOutIndex] = leftIndex;
+ }
+ ++rightPtr;
+ }
+ leftPtr = rightPtr;
+ }
+#if DEBUG_OUT
+ int* mismatch = fMismatches.begin();
+ while (mismatch != fMismatches.end()) {
+ int leftIndex = *mismatch++;
+ int leftOutIndex = abs(leftIndex) - 1;
+ const OutEdge& left = fEdges[leftOutIndex];
+ const SkPoint& leftPt = left.fPts[leftIndex < 0 ? 0 : left.fVerb];
+ SkDebugf("%s left=%d %s (%1.9g,%1.9g)\n",
+ __FUNCTION__, left.fID, leftIndex < 0 ? "top" : "bot",
+ leftPt.fX, leftPt.fY);
+ }
+ SkASSERT(fMismatches.count() == 0);
+#endif
+#if DEBUG_BRIDGE
+ for (index = 0; index < count; ++index) {
+ const OutEdge& edge = fEdges[index];
+ uint8_t verb = edge.fVerb;
+ SkDebugf("%s %d edge=%d %s (%1.9g,%1.9g) (%1.9g,%1.9g)\n",
+ index == 0 ? __FUNCTION__ : " ",
+ index + 1, edge.fID, kLVerbStr[verb], edge.fPts[0].fX,
+ edge.fPts[0].fY, edge.fPts[verb].fX, edge.fPts[verb].fY);
+ }
+ for (index = 0; index < count; ++index) {
+ SkDebugf(" top of % 2d connects to %s of % 2d\n", index + 1,
+ fTops[index] < 0 ? "top " : "bottom", abs(fTops[index]));
+ SkDebugf(" bottom of % 2d connects to %s of % 2d\n", index + 1,
+ fBottoms[index] < 0 ? "top " : "bottom", abs(fBottoms[index]));
+ }
+#endif
+ }
+
+protected:
+ SkTArray<OutEdge> fEdges;
+ SkTDArray<int> fTops;
+ SkTDArray<int> fBottoms;
+ bool fFill;
+#if DEBUG_OUT
+ SkTDArray<int> fMismatches;
+#endif
+};
+
+// Bounds, unlike Rect, does not consider a vertical line to be empty.
+struct Bounds : public SkRect {
+ static bool Intersects(const Bounds& a, const Bounds& b) {
+ return a.fLeft <= b.fRight && b.fLeft <= a.fRight &&
+ a.fTop <= b.fBottom && b.fTop <= a.fBottom;
+ }
+
+ bool isEmpty() {
+ return fLeft > fRight || fTop > fBottom
+ || (fLeft == fRight && fTop == fBottom)
+ || isnan(fLeft) || isnan(fRight)
+ || isnan(fTop) || isnan(fBottom);
+ }
+};
+
+class Intercepts {
+public:
+ Intercepts()
+ : fTopIntercepts(0)
+ , fBottomIntercepts(0)
+ , fExplicit(false) {
+ }
+
+ Intercepts& operator=(const Intercepts& src) {
+ fTs = src.fTs;
+ fTopIntercepts = src.fTopIntercepts;
+ fBottomIntercepts = src.fBottomIntercepts;
+ return *this;
+ }
+
+ // OPTIMIZATION: remove this function if it's never called
+ double t(int tIndex) const {
+ if (tIndex == 0) {
+ return 0;
+ }
+ if (tIndex > fTs.count()) {
+ return 1;
+ }
+ return fTs[tIndex - 1];
+ }
+
+#if DEBUG_DUMP
+ void dump(const SkPoint* pts, SkPath::Verb verb) {
+ const char className[] = "Intercepts";
+ const int tab = 8;
+ for (int i = 0; i < fTs.count(); ++i) {
+ SkPoint out;
+ switch (verb) {
+ case SkPath::kLine_Verb:
+ LineXYAtT(pts, fTs[i], &out);
+ break;
+ case SkPath::kQuad_Verb:
+ QuadXYAtT(pts, fTs[i], &out);
+ break;
+ case SkPath::kCubic_Verb:
+ CubicXYAtT(pts, fTs[i], &out);
+ break;
+ default:
+ SkASSERT(0);
+ }
+ SkDebugf("%*s.fTs[%d]=%1.9g (%1.9g,%1.9g)\n", tab + sizeof(className),
+ className, i, fTs[i], out.fX, out.fY);
+ }
+ SkDebugf("%*s.fTopIntercepts=%u\n", tab + sizeof(className),
+ className, fTopIntercepts);
+ SkDebugf("%*s.fBottomIntercepts=%u\n", tab + sizeof(className),
+ className, fBottomIntercepts);
+ SkDebugf("%*s.fExplicit=%d\n", tab + sizeof(className),
+ className, fExplicit);
+ }
+#endif
+
+ SkTDArray<double> fTs;
+ unsigned char fTopIntercepts; // 0=init state 1=1 edge >1=multiple edges
+ unsigned char fBottomIntercepts;
+ bool fExplicit; // if set, suppress 0 and 1
+
+};
+
+struct HorizontalEdge {
+ bool operator<(const HorizontalEdge& rh) const {
+ return fY == rh.fY ? fLeft == rh.fLeft ? fRight < rh.fRight
+ : fLeft < rh.fLeft : fY < rh.fY;
+ }
+
+#if DEBUG_DUMP
+ void dump() {
+ const char className[] = "HorizontalEdge";
+ const int tab = 4;
+ SkDebugf("%*s.fLeft=%1.9g\n", tab + sizeof(className), className, fLeft);
+ SkDebugf("%*s.fRight=%1.9g\n", tab + sizeof(className), className, fRight);
+ SkDebugf("%*s.fY=%1.9g\n", tab + sizeof(className), className, fY);
+ }
+#endif
+
+ SkScalar fLeft;
+ SkScalar fRight;
+ SkScalar fY;
+};
+
+struct InEdge {
+ bool operator<(const InEdge& rh) const {
+ return fBounds.fTop == rh.fBounds.fTop
+ ? fBounds.fLeft < rh.fBounds.fLeft
+ : fBounds.fTop < rh.fBounds.fTop;
+ }
+
+ // Avoid collapsing t values that are close to the same since
+ // we walk ts to describe consecutive intersections. Since a pair of ts can
+ // be nearly equal, any problems caused by this should be taken care
+ // of later.
+ int add(double* ts, size_t count, ptrdiff_t verbIndex) {
+ // FIXME: in the pathological case where there is a ton of intercepts, binary search?
+ bool foundIntercept = false;
+ int insertedAt = -1;
+ Intercepts& intercepts = fIntercepts[verbIndex];
+ for (size_t index = 0; index < count; ++index) {
+ double t = ts[index];
+ if (t <= 0) {
+ intercepts.fTopIntercepts <<= 1;
+ fContainsIntercepts |= ++intercepts.fTopIntercepts > 1;
+ continue;
+ }
+ if (t >= 1) {
+ intercepts.fBottomIntercepts <<= 1;
+ fContainsIntercepts |= ++intercepts.fBottomIntercepts > 1;
+ continue;
+ }
+ fIntersected = true;
+ foundIntercept = true;
+ size_t tCount = intercepts.fTs.count();
+ double delta;
+ for (size_t idx2 = 0; idx2 < tCount; ++idx2) {
+ if (t <= intercepts.fTs[idx2]) {
+ // FIXME: ? if (t < intercepts.fTs[idx2]) // failed
+ delta = intercepts.fTs[idx2] - t;
+ if (delta > 0) {
+ insertedAt = idx2;
+ *intercepts.fTs.insert(idx2) = t;
+ }
+ goto nextPt;
+ }
+ }
+ if (tCount == 0 || (delta = t - intercepts.fTs[tCount - 1]) > 0) {
+ insertedAt = tCount;
+ *intercepts.fTs.append() = t;
+ }
+ nextPt:
+ ;
+ }
+ fContainsIntercepts |= foundIntercept;
+ return insertedAt;
+ }
+
+ void addPartial(SkTArray<InEdge>& edges, int ptStart, int ptEnd,
+ int verbStart, int verbEnd) {
+ InEdge* edge = edges.push_back_n(1);
+ int verbCount = verbEnd - verbStart;
+ edge->fIntercepts.push_back_n(verbCount);
+ // uint8_t* verbs = &fVerbs[verbStart];
+ for (int ceptIdx = 0; ceptIdx < verbCount; ++ceptIdx) {
+ edge->fIntercepts[ceptIdx] = fIntercepts[verbStart + ceptIdx];
+ }
+ edge->fPts.append(ptEnd - ptStart, &fPts[ptStart]);
+ edge->fVerbs.append(verbCount, &fVerbs[verbStart]);
+ edge->setBounds();
+ edge->fWinding = fWinding;
+ edge->fContainsIntercepts = fContainsIntercepts; // FIXME: may not be correct -- but do we need to know?
+ }
+
+ void addSplit(SkTArray<InEdge>& edges, SkPoint* pts, uint8_t verb,
+ Intercepts& intercepts, int firstT, int lastT, bool flipped) {
+ InEdge* edge = edges.push_back_n(1);
+ edge->fIntercepts.push_back_n(1);
+ if (firstT == 0) {
+ *edge->fIntercepts[0].fTs.append() = 0;
+ } else {
+ *edge->fIntercepts[0].fTs.append() = intercepts.fTs[firstT - 1];
+ }
+ bool add1 = lastT == intercepts.fTs.count();
+ edge->fIntercepts[0].fTs.append(lastT - firstT, &intercepts.fTs[firstT]);
+ if (add1) {
+ *edge->fIntercepts[0].fTs.append() = 1;
+ }
+ edge->fIntercepts[0].fExplicit = true;
+ edge->fPts.append(verb + 1, pts);
+ edge->fVerbs.append(1, &verb);
+ // FIXME: bounds could be better for partial Ts
+ edge->setSubBounds();
+ edge->fContainsIntercepts = fContainsIntercepts; // FIXME: may not be correct -- but do we need to know?
+ if (flipped) {
+ edge->flipTs();
+ edge->fWinding = -fWinding;
+ } else {
+ edge->fWinding = fWinding;
+ }
+ }
+
+ bool cached(const InEdge* edge) {
+ // FIXME: in the pathological case where there is a ton of edges, binary search?
+ size_t count = fCached.count();
+ for (size_t index = 0; index < count; ++index) {
+ if (edge == fCached[index]) {
+ return true;
+ }
+ if (edge < fCached[index]) {
+ *fCached.insert(index) = edge;
+ return false;
+ }
+ }
+ *fCached.append() = edge;
+ return false;
+ }
+
+ void complete(signed char winding) {
+ setBounds();
+ fIntercepts.push_back_n(fVerbs.count());
+ if ((fWinding = winding) < 0) { // reverse verbs, pts, if bottom to top
+ flip();
+ }
+ fContainsIntercepts = fIntersected = false;
+ }
+
+ void flip() {
+ size_t index;
+ size_t last = fPts.count() - 1;
+ for (index = 0; index < last; ++index, --last) {
+ SkTSwap<SkPoint>(fPts[index], fPts[last]);
+ }
+ last = fVerbs.count() - 1;
+ for (index = 0; index < last; ++index, --last) {
+ SkTSwap<uint8_t>(fVerbs[index], fVerbs[last]);
+ }
+ }
+
+ void flipTs() {
+ SkASSERT(fIntercepts.count() == 1);
+ Intercepts& intercepts = fIntercepts[0];
+ SkASSERT(intercepts.fExplicit);
+ SkTDArray<double>& ts = intercepts.fTs;
+ size_t index;
+ size_t last = ts.count() - 1;
+ for (index = 0; index < last; ++index, --last) {
+ SkTSwap<double>(ts[index], ts[last]);
+ }
+ }
+
+ void reset() {
+ fCached.reset();
+ fIntercepts.reset();
+ fPts.reset();
+ fVerbs.reset();
+ fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax);
+ fWinding = 0;
+ fContainsIntercepts = false;
+ fIntersected = false;
+ }
+
+ void setBounds() {
+ SkPoint* ptPtr = fPts.begin();
+ SkPoint* ptLast = fPts.end();
+ if (ptPtr == ptLast) {
+ SkDebugf("%s empty edge\n", __FUNCTION__);
+ SkASSERT(0);
+ // FIXME: delete empty edge?
+ return;
+ }
+ fBounds.set(ptPtr->fX, ptPtr->fY, ptPtr->fX, ptPtr->fY);
+ ++ptPtr;
+ while (ptPtr != ptLast) {
+ fBounds.growToInclude(ptPtr->fX, ptPtr->fY);
+ ++ptPtr;
+ }
+ }
+
+ // recompute bounds based on subrange of T values
+ void setSubBounds() {
+ SkASSERT(fIntercepts.count() == 1);
+ Intercepts& intercepts = fIntercepts[0];
+ SkASSERT(intercepts.fExplicit);
+ SkASSERT(fVerbs.count() == 1);
+ SkTDArray<double>& ts = intercepts.fTs;
+ if (fVerbs[0] == SkPath::kQuad_Verb) {
+ SkASSERT(fPts.count() == 3);
+ QuadSubBounds(fPts.begin(), ts[0], ts[ts.count() - 1], fBounds);
+ } else {
+ SkASSERT(fVerbs[0] == SkPath::kCubic_Verb);
+ SkASSERT(fPts.count() == 4);
+ CubicSubBounds(fPts.begin(), ts[0], ts[ts.count() - 1], fBounds);
+ }
+ }
+
+ void splitInflectionPts(SkTArray<InEdge>& edges) {
+ if (!fIntersected) {
+ return;
+ }
+ uint8_t* verbs = fVerbs.begin();
+ SkPoint* pts = fPts.begin();
+ int lastVerb = 0;
+ int lastPt = 0;
+ uint8_t verb;
+ bool edgeSplit = false;
+ for (int ceptIdx = 0; ceptIdx < fIntercepts.count(); ++ceptIdx, pts += verb) {
+ Intercepts& intercepts = fIntercepts[ceptIdx];
+ verb = *verbs++;
+ if (verb <= SkPath::kLine_Verb) {
+ continue;
+ }
+ size_t tCount = intercepts.fTs.count();
+ if (!tCount) {
+ continue;
+ }
+ size_t tIndex = (size_t) -1;
+ SkScalar y = pts[0].fY;
+ int lastSplit = 0;
+ int firstSplit = -1;
+ bool curveSplit = false;
+ while (++tIndex < tCount) {
+ double nextT = intercepts.fTs[tIndex];
+ SkScalar nextY = verb == SkPath::kQuad_Verb
+ ? QuadYAtT(pts, nextT) : CubicYAtT(pts, nextT);
+ if (nextY < y) {
+ edgeSplit = curveSplit = true;
+ if (firstSplit < 0) {
+ firstSplit = tIndex;
+ int nextPt = pts - fPts.begin();
+ int nextVerb = verbs - 1 - fVerbs.begin();
+ if (lastVerb < nextVerb) {
+ addPartial(edges, lastPt, nextPt, lastVerb, nextVerb);
+ #if DEBUG_SPLIT
+ SkDebugf("%s addPartial 1\n", __FUNCTION__);
+ #endif
+ }
+ lastPt = nextPt;
+ lastVerb = nextVerb;
+ }
+ } else {
+ if (firstSplit >= 0) {
+ if (lastSplit < firstSplit) {
+ addSplit(edges, pts, verb, intercepts,
+ lastSplit, firstSplit, false);
+ #if DEBUG_SPLIT
+ SkDebugf("%s addSplit 1 tIndex=%d,%d\n",
+ __FUNCTION__, lastSplit, firstSplit);
+ #endif
+ }
+ addSplit(edges, pts, verb, intercepts,
+ firstSplit, tIndex, true);
+ #if DEBUG_SPLIT
+ SkDebugf("%s addSplit 2 tIndex=%d,%d flip\n",
+ __FUNCTION__, firstSplit, tIndex);
+ #endif
+ lastSplit = tIndex;
+ firstSplit = -1;
+ }
+ }
+ y = nextY;
+ }
+ if (curveSplit) {
+ if (firstSplit < 0) {
+ firstSplit = lastSplit;
+ } else {
+ addSplit(edges, pts, verb, intercepts, lastSplit,
+ firstSplit, false);
+ #if DEBUG_SPLIT
+ SkDebugf("%s addSplit 3 tIndex=%d,%d\n", __FUNCTION__,
+ lastSplit, firstSplit);
+ #endif
+ }
+ addSplit(edges, pts, verb, intercepts, firstSplit,
+ tIndex, pts[verb].fY < y);
+ #if DEBUG_SPLIT
+ SkDebugf("%s addSplit 4 tIndex=%d,%d %s\n", __FUNCTION__,
+ firstSplit, tIndex, pts[verb].fY < y ? "flip" : "");
+ #endif
+ }
+ }
+ // collapse remainder -- if there's nothing left, clear it somehow?
+ if (edgeSplit) {
+ int nextVerb = verbs - 1 - fVerbs.begin();
+ if (lastVerb < nextVerb) {
+ int nextPt = pts - fPts.begin();
+ addPartial(edges, lastPt, nextPt, lastVerb, nextVerb);
+ #if DEBUG_SPLIT
+ SkDebugf("%s addPartial 2\n", __FUNCTION__);
+ #endif
+ }
+ // OPTIMIZATION: reuse the edge instead of marking it empty
+ reset();
+ }
+ }
+
+#if DEBUG_DUMP
+ void dump() {
+ int i;
+ const char className[] = "InEdge";
+ const int tab = 4;
+ SkDebugf("InEdge %p (edge=%d)\n", this, fID);
+ for (i = 0; i < fCached.count(); ++i) {
+ SkDebugf("%*s.fCached[%d]=0x%08x\n", tab + sizeof(className),
+ className, i, fCached[i]);
+ }
+ uint8_t* verbs = fVerbs.begin();
+ SkPoint* pts = fPts.begin();
+ for (i = 0; i < fIntercepts.count(); ++i) {
+ SkDebugf("%*s.fIntercepts[%d]:\n", tab + sizeof(className),
+ className, i);
+ fIntercepts[i].dump(pts, (SkPath::Verb) *verbs);
+ pts += *verbs++;
+ }
+ for (i = 0; i < fPts.count(); ++i) {
+ SkDebugf("%*s.fPts[%d]=(%1.9g,%1.9g)\n", tab + sizeof(className),
+ className, i, fPts[i].fX, fPts[i].fY);
+ }
+ for (i = 0; i < fVerbs.count(); ++i) {
+ SkDebugf("%*s.fVerbs[%d]=%d\n", tab + sizeof(className),
+ className, i, fVerbs[i]);
+ }
+ SkDebugf("%*s.fBounds=(%1.9g, %1.9g, %1.9g, %1.9g)\n", tab + sizeof(className),
+ className, fBounds.fLeft, fBounds.fTop,
+ fBounds.fRight, fBounds.fBottom);
+ SkDebugf("%*s.fWinding=%d\n", tab + sizeof(className), className,
+ fWinding);
+ SkDebugf("%*s.fContainsIntercepts=%d\n", tab + sizeof(className),
+ className, fContainsIntercepts);
+ SkDebugf("%*s.fIntersected=%d\n", tab + sizeof(className),
+ className, fIntersected);
+ }
+#endif
+
+ // FIXME: temporary data : move this to a separate struct?
+ SkTDArray<const InEdge*> fCached; // list of edges already intercepted
+ SkTArray<Intercepts> fIntercepts; // one per verb
+
+ // persistent data
+ SkTDArray<SkPoint> fPts;
+ SkTDArray<uint8_t> fVerbs;
+ Bounds fBounds;
+ int fID;
+ signed char fWinding;
+ bool fContainsIntercepts;
+ bool fIntersected;
+};
+
+class InEdgeBuilder {
+public:
+
+InEdgeBuilder(const SkPath& path, bool ignoreHorizontal, SkTArray<InEdge>& edges,
+ SkTDArray<HorizontalEdge>& horizontalEdges)
+ : fPath(path)
+ , fCurrentEdge(NULL)
+ , fEdges(edges)
+ , fHorizontalEdges(horizontalEdges)
+ , fIgnoreHorizontal(ignoreHorizontal)
+ , fContainsCurves(false)
+{
+ walk();
+}
+
+bool containsCurves() const {
+ return fContainsCurves;
+}
+
+protected:
+
+void addEdge() {
+ SkASSERT(fCurrentEdge);
+ fCurrentEdge->fPts.append(fPtCount - fPtOffset, &fPts[fPtOffset]);
+ fPtOffset = 1;
+ *fCurrentEdge->fVerbs.append() = fVerb;
+}
+
+bool complete() {
+ if (fCurrentEdge && fCurrentEdge->fVerbs.count()) {
+ fCurrentEdge->complete(fWinding);
+ fCurrentEdge = NULL;
+ return true;
+ }
+ return false;
+}
+
+int direction(SkPath::Verb verb) {
+ fPtCount = verb + 1;
+ if (fIgnoreHorizontal && isHorizontal()) {
+ return 0;
+ }
+ return fPts[0].fY == fPts[verb].fY
+ ? fPts[0].fX == fPts[verb].fX ? 0 : fPts[0].fX < fPts[verb].fX
+ ? 1 : -1 : fPts[0].fY < fPts[verb].fY ? 1 : -1;
+}
+
+bool isHorizontal() {
+ SkScalar y = fPts[0].fY;
+ for (int i = 1; i < fPtCount; ++i) {
+ if (fPts[i].fY != y) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void startEdge() {
+ if (!fCurrentEdge) {
+ fCurrentEdge = fEdges.push_back_n(1);
+ }
+ fWinding = 0;
+ fPtOffset = 0;
+}
+
+void walk() {
+ SkPath::Iter iter(fPath, true);
+ int winding = 0;
+ while ((fVerb = iter.next(fPts)) != SkPath::kDone_Verb) {
+ switch (fVerb) {
+ case SkPath::kMove_Verb:
+ startEdge();
+ continue;
+ case SkPath::kLine_Verb:
+ winding = direction(SkPath::kLine_Verb);
+ break;
+ case SkPath::kQuad_Verb:
+ fVerb = QuadReduceOrder(fPts);
+ winding = direction(fVerb);
+ fContainsCurves |= fVerb == SkPath::kQuad_Verb;
+ break;
+ case SkPath::kCubic_Verb:
+ fVerb = CubicReduceOrder(fPts);
+ winding = direction(fVerb);
+ fContainsCurves |= fVerb >= SkPath::kQuad_Verb;
+ break;
+ case SkPath::kClose_Verb:
+ SkASSERT(fCurrentEdge);
+ complete();
+ continue;
+ default:
+ SkDEBUGFAIL("bad verb");
+ return;
+ }
+ if (winding == 0) {
+ HorizontalEdge* horizontalEdge = fHorizontalEdges.append();
+ // FIXME: for degenerate quads and cubics, compute x extremes
+ horizontalEdge->fLeft = fPts[0].fX;
+ horizontalEdge->fRight = fPts[fVerb].fX;
+ horizontalEdge->fY = fPts[0].fY;
+ if (horizontalEdge->fLeft > horizontalEdge->fRight) {
+ SkTSwap<SkScalar>(horizontalEdge->fLeft, horizontalEdge->fRight);
+ }
+ if (complete()) {
+ startEdge();
+ }
+ continue;
+ }
+ if (fWinding + winding == 0) {
+ // FIXME: if prior verb or this verb is a horizontal line, reverse
+ // it instead of starting a new edge
+ SkASSERT(fCurrentEdge);
+ if (complete()) {
+ startEdge();
+ }
+ }
+ fWinding = winding;
+ addEdge();
+ }
+ if (!complete()) {
+ if (fCurrentEdge) {
+ fEdges.pop_back();
+ }
+ }
+}
+
+private:
+ const SkPath& fPath;
+ InEdge* fCurrentEdge;
+ SkTArray<InEdge>& fEdges;
+ SkTDArray<HorizontalEdge>& fHorizontalEdges;
+ SkPoint fPts[4];
+ SkPath::Verb fVerb;
+ int fPtCount;
+ int fPtOffset;
+ int8_t fWinding;
+ bool fIgnoreHorizontal;
+ bool fContainsCurves;
+};
+
+struct WorkEdge {
+ SkScalar bottom() const {
+ return fPts[verb()].fY;
+ }
+
+ void init(const InEdge* edge) {
+ fEdge = edge;
+ fPts = edge->fPts.begin();
+ fVerb = edge->fVerbs.begin();
+ }
+
+ bool advance() {
+ SkASSERT(fVerb < fEdge->fVerbs.end());
+ fPts += *fVerb++;
+ return fVerb != fEdge->fVerbs.end();
+ }
+
+ const SkPoint* lastPoints() const {
+ SkASSERT(fPts >= fEdge->fPts.begin() + lastVerb());
+ return &fPts[-lastVerb()];
+ }
+
+ SkPath::Verb lastVerb() const {
+ SkASSERT(fVerb > fEdge->fVerbs.begin());
+ return (SkPath::Verb) fVerb[-1];
+ }
+
+ const SkPoint* points() const {
+ return fPts;
+ }
+
+ SkPath::Verb verb() const {
+ return (SkPath::Verb) *fVerb;
+ }
+
+ ptrdiff_t verbIndex() const {
+ return fVerb - fEdge->fVerbs.begin();
+ }
+
+ int winding() const {
+ return fEdge->fWinding;
+ }
+
+ const InEdge* fEdge;
+ const SkPoint* fPts;
+ const uint8_t* fVerb;
+};
+
+// always constructed with SkTDArray because new edges are inserted
+// this may be a inappropriate optimization, suggesting that a separate array of
+// ActiveEdge* may be faster to insert and search
+
+// OPTIMIZATION: Brian suggests that global sorting should be unnecessary, since
+// as active edges are introduced, only local sorting should be required
+class ActiveEdge {
+public:
+ // this logic must be kept in sync with tooCloseToCall
+ // callers expect this to only read fAbove, fTangent
+ bool operator<(const ActiveEdge& rh) const {
+ if (fVerb == rh.fVerb) {
+ // FIXME: don't know what to do if verb is quad, cubic
+ return abCompare(fAbove, fBelow, rh.fAbove, rh.fBelow);
+ }
+ // figure out which is quad, line
+ // if cached data says line did not intersect quad, use top/bottom
+ if (fVerb != SkPath::kLine_Verb ? noIntersect(rh) : rh.noIntersect(*this)) {
+ return abCompare(fAbove, fBelow, rh.fAbove, rh.fBelow);
+ }
+ // use whichever of top/tangent tangent/bottom overlaps more
+ // with line top/bot
+ // assumes quad/cubic can already be upconverted to cubic/cubic
+ const SkPoint* line[2];
+ const SkPoint* curve[4];
+ if (fVerb != SkPath::kLine_Verb) {
+ line[0] = &rh.fAbove;
+ line[1] = &rh.fBelow;
+ curve[0] = &fAbove;
+ curve[1] = &fTangent;
+ curve[2] = &fBelow;
+ } else {
+ line[0] = &fAbove;
+ line[1] = &fBelow;
+ curve[0] = &rh.fAbove;
+ curve[1] = &rh.fTangent;
+ curve[2] = &rh.fBelow;
+ }
+ // FIXME: code has been abandoned, incomplete....
+ return false;
+ }
+
+ bool abCompare(const SkPoint& a1, const SkPoint& a2, const SkPoint& b1,
+ const SkPoint& b2) const {
+ double topD = a1.fX - b1.fX;
+ if (b1.fY < a1.fY) {
+ topD = (b2.fY - b1.fY) * topD - (a1.fY - b1.fY) * (b2.fX - b1.fX);
+ } else if (b1.fY > a1.fY) {
+ topD = (a2.fY - a1.fY) * topD + (b1.fY - a1.fY) * (a2.fX - a1.fX);
+ }
+ double botD = a2.fX - b2.fX;
+ if (b2.fY > a2.fY) {
+ botD = (b2.fY - b1.fY) * botD - (a2.fY - b2.fY) * (b2.fX - b1.fX);
+ } else if (b2.fY < a2.fY) {
+ botD = (a2.fY - a1.fY) * botD + (b2.fY - a2.fY) * (a2.fX - a1.fX);
+ }
+ // return sign of greater absolute value
+ return (fabs(topD) > fabs(botD) ? topD : botD) < 0;
+ }
+
+ // If a pair of edges are nearly coincident for some span, add a T in the
+ // edge so it can be shortened to match the other edge. Note that another
+ // approach is to trim the edge after it is added to the OutBuilder list --
+ // FIXME: since this has no effect if the edge is already done (i.e.,
+ // fYBottom >= y) maybe this can only be done by calling trimLine later.
+ void addTatYBelow(SkScalar y) {
+ if (fBelow.fY <= y || fYBottom >= y) {
+ return;
+ }
+ addTatYInner(y);
+ fFixBelow = true;
+ }
+
+ void addTatYAbove(SkScalar y) {
+ if (fBelow.fY <= y) {
+ return;
+ }
+ addTatYInner(y);
+ }
+
+ void addTatYInner(SkScalar y) {
+ if (fWorkEdge.fPts[0].fY > y) {
+ backup(y);
+ }
+ SkScalar left = fWorkEdge.fPts[0].fX;
+ SkScalar right = fWorkEdge.fPts[1].fX;
+ if (left > right) {
+ SkTSwap(left, right);
+ }
+ double ts[2];
+ SkASSERT(fWorkEdge.fVerb[0] == SkPath::kLine_Verb);
+ int pts = LineIntersect(fWorkEdge.fPts, left, right, y, ts);
+ SkASSERT(pts == 1);
+ // An ActiveEdge or WorkEdge has no need to modify the T values computed
+ // in the InEdge, except in the following case. If a pair of edges are
+ // nearly coincident, this may not be detected when the edges are
+ // intersected. Later, when sorted, and this near-coincidence is found,
+ // an additional t value must be added, requiring the cast below.
+ InEdge* writable = const_cast<InEdge*>(fWorkEdge.fEdge);
+ int insertedAt = writable->add(ts, pts, fWorkEdge.verbIndex());
+ #if DEBUG_ADJUST_COINCIDENT
+ SkDebugf("%s edge=%d y=%1.9g t=%1.9g\n", __FUNCTION__, ID(), y, ts[0]);
+ #endif
+ if (insertedAt >= 0) {
+ if (insertedAt + 1 < fTIndex) {
+ SkASSERT(insertedAt + 2 == fTIndex);
+ --fTIndex;
+ }
+ }
+ }
+
+ bool advanceT() {
+ SkASSERT(fTIndex <= fTs->count() - fExplicitTs);
+ return ++fTIndex <= fTs->count() - fExplicitTs;
+ }
+
+ bool advance() {
+ // FIXME: flip sense of next
+ bool result = fWorkEdge.advance();
+ fDone = !result;
+ initT();
+ return result;
+ }
+
+ void backup(SkScalar y) {
+ do {
+ SkASSERT(fWorkEdge.fEdge->fVerbs.begin() < fWorkEdge.fVerb);
+ fWorkEdge.fPts -= *--fWorkEdge.fVerb;
+ SkASSERT(fWorkEdge.fEdge->fPts.begin() <= fWorkEdge.fPts);
+ } while (fWorkEdge.fPts[0].fY >= y);
+ initT();
+ SkASSERT(!fExplicitTs);
+ fTIndex = fTs->count() + 1;
+ }
+
+ void calcAboveBelow(double tAbove, double tBelow) {
+ fVerb = fWorkEdge.verb();
+ switch (fVerb) {
+ case SkPath::kLine_Verb:
+ LineXYAtT(fWorkEdge.fPts, tAbove, &fAbove);
+ LineXYAtT(fWorkEdge.fPts, tBelow, &fTangent);
+ fBelow = fTangent;
+ break;
+ case SkPath::kQuad_Verb:
+ // FIXME: put array in struct to avoid copy?
+ SkPoint quad[3];
+ QuadSubDivide(fWorkEdge.fPts, tAbove, tBelow, quad);
+ fAbove = quad[0];
+ fTangent = quad[0] != quad[1] ? quad[1] : quad[2];
+ fBelow = quad[2];
+ break;
+ case SkPath::kCubic_Verb:
+ SkPoint cubic[3];
+ CubicSubDivide(fWorkEdge.fPts, tAbove, tBelow, cubic);
+ fAbove = cubic[0];
+ // FIXME: can't see how quad logic for how tangent is used
+ // extends to cubic
+ fTangent = cubic[0] != cubic[1] ? cubic[1]
+ : cubic[0] != cubic[2] ? cubic[2] : cubic[3];
+ fBelow = cubic[3];
+ break;
+ default:
+ SkASSERT(0);
+ }
+ }
+
+ void calcLeft(SkScalar y) {
+ // OPTIMIZE: put a kDone_Verb at the end of the verb list?
+ if (fDone || fBelow.fY > y) {
+ return; // nothing to do; use last
+ }
+ calcLeft();
+ if (fAbove.fY == fBelow.fY) {
+ SkDebugf("%s edge=%d fAbove.fY != fBelow.fY %1.9g\n", __FUNCTION__,
+ ID(), fAbove.fY);
+ }
+ }
+
+ void calcLeft() {
+ int add = (fTIndex <= fTs->count() - fExplicitTs) - 1;
+ double tAbove = t(fTIndex + add);
+ double tBelow = t(fTIndex - ~add);
+ // OPTIMIZATION: if fAbove, fBelow have already been computed
+ // for the fTIndex, don't do it again
+ calcAboveBelow(tAbove, tBelow);
+ // For identical x, this lets us know which edge is first.
+ // If both edges have T values < 1, check x at next T (fBelow).
+ SkASSERT(tAbove != tBelow);
+ // FIXME: this can loop forever
+ // need a break if we hit the end
+ // FIXME: in unit test, figure out how explicit Ts work as well
+ while (fAbove.fY == fBelow.fY) {
+ if (add < 0 || fTIndex == fTs->count()) {
+ add -= 1;
+ SkASSERT(fTIndex + add >= 0);
+ tAbove = t(fTIndex + add);
+ } else {
+ add += 1;
+ SkASSERT(fTIndex - ~add <= fTs->count() + 1);
+ tBelow = t(fTIndex - ~add);
+ }
+ calcAboveBelow(tAbove, tBelow);
+ }
+ fTAbove = tAbove;
+ fTBelow = tBelow;
+ }
+
+ bool done(SkScalar bottom) const {
+ return fDone || fYBottom >= bottom;
+ }
+
+ void fixBelow() {
+ if (fFixBelow) {
+ fTBelow = nextT();
+ calcAboveBelow(fTAbove, fTBelow);
+ fFixBelow = false;
+ }
+ }
+
+ void init(const InEdge* edge) {
+ fWorkEdge.init(edge);
+ fDone = false;
+ initT();
+ fBelow.fY = SK_ScalarMin;
+ fYBottom = SK_ScalarMin;
+ }
+
+ void initT() {
+ const Intercepts& intercepts = fWorkEdge.fEdge->fIntercepts.front();
+ SkASSERT(fWorkEdge.verbIndex() <= fWorkEdge.fEdge->fIntercepts.count());
+ const Intercepts* interceptPtr = &intercepts + fWorkEdge.verbIndex();
+ fTs = &interceptPtr->fTs;
+ fExplicitTs = interceptPtr->fExplicit;
+ // the above is conceptually the same as
+ // fTs = &fWorkEdge.fEdge->fIntercepts[fWorkEdge.verbIndex()].fTs;
+ // but templated arrays don't allow returning a pointer to the end() element
+ fTIndex = 0;
+ if (!fDone) {
+ fVerb = fWorkEdge.verb();
+ }
+ SkASSERT(fVerb > SkPath::kMove_Verb);
+ }
+
+ // OPTIMIZATION: record if two edges are coincident when the are intersected
+ // It's unclear how to do this -- seems more complicated than recording the
+ // t values, since the same t values could exist intersecting non-coincident
+ // edges.
+ bool isCoincidentWith(const ActiveEdge* edge) const {
+ if (fAbove != edge->fAbove || fBelow != edge->fBelow) {
+ return false;
+ }
+ if (fVerb != edge->fVerb) {
+ return false;
+ }
+ switch (fVerb) {
+ case SkPath::kLine_Verb:
+ return true;
+ default:
+ // FIXME: add support for quads, cubics
+ SkASSERT(0);
+ return false;
+ }
+ return false;
+ }
+
+ bool isUnordered(const ActiveEdge* edge) const {
+ return fAbove == edge->fAbove && fBelow == edge->fBelow;
+ }
+
+// SkPath::Verb lastVerb() const {
+// return fDone ? fWorkEdge.lastVerb() : fWorkEdge.verb();
+// }
+
+ const SkPoint* lastPoints() const {
+ return fDone ? fWorkEdge.lastPoints() : fWorkEdge.points();
+ }
+
+ bool noIntersect(const ActiveEdge& ) const {
+ // incomplete
+ return false;
+ }
+
+ // The shortest close call edge should be moved into a position where
+ // it contributes if the winding is transitioning to or from zero.
+ bool swapClose(const ActiveEdge* next, int prev, int wind, int mask) const {
+#if DEBUG_ADJUST_COINCIDENT
+ SkDebugf("%s edge=%d (%g) next=%d (%g) prev=%d wind=%d nextWind=%d\n",
+ __FUNCTION__, ID(), fBelow.fY, next->ID(), next->fBelow.fY,
+ prev, wind, wind + next->fWorkEdge.winding());
+#endif
+ if ((prev & mask) == 0 || (wind & mask) == 0) {
+ return next->fBelow.fY < fBelow.fY;
+ }
+ int nextWinding = wind + next->fWorkEdge.winding();
+ if ((nextWinding & mask) == 0) {
+ return fBelow.fY < next->fBelow.fY;
+ }
+ return false;
+ }
+
+ bool swapCoincident(const ActiveEdge* edge, SkScalar bottom) const {
+ if (fBelow.fY >= bottom || fDone || edge->fDone) {
+ return false;
+ }
+ ActiveEdge thisWork = *this;
+ ActiveEdge edgeWork = *edge;
+ while ((thisWork.advanceT() || thisWork.advance())
+ && (edgeWork.advanceT() || edgeWork.advance())) {
+ thisWork.calcLeft();
+ edgeWork.calcLeft();
+ if (thisWork < edgeWork) {
+ return false;
+ }
+ if (edgeWork < thisWork) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool swapUnordered(const ActiveEdge* edge, SkScalar /* bottom */) const {
+ SkASSERT(fVerb != SkPath::kLine_Verb
+ || edge->fVerb != SkPath::kLine_Verb);
+ if (fDone || edge->fDone) {
+ return false;
+ }
+ ActiveEdge thisWork, edgeWork;
+ extractAboveBelow(thisWork);
+ edge->extractAboveBelow(edgeWork);
+ return edgeWork < thisWork;
+ }
+
+ bool tooCloseToCall(const ActiveEdge* edge) const {
+ int ulps;
+ double t1, t2, b1, b2;
+ // This logic must be kept in sync with operator <
+ if (edge->fAbove.fY < fAbove.fY) {
+ t1 = (edge->fTangent.fY - edge->fAbove.fY) * (fAbove.fX - edge->fAbove.fX);
+ t2 = (fAbove.fY - edge->fAbove.fY) * (edge->fTangent.fX - edge->fAbove.fX);
+ } else if (edge->fAbove.fY > fAbove.fY) {
+ t1 = (fTangent.fY - fAbove.fY) * (fAbove.fX - edge->fAbove.fX);
+ t2 = (fAbove.fY - edge->fAbove.fY) * (fTangent.fX - fAbove.fX);
+ } else {
+ t1 = fAbove.fX;
+ t2 = edge->fAbove.fX;
+ }
+ if (edge->fTangent.fY > fTangent.fY) {
+ b1 = (edge->fTangent.fY - edge->fAbove.fY) * (fTangent.fX - edge->fTangent.fX);
+ b2 = (fTangent.fY - edge->fTangent.fY) * (edge->fTangent.fX - edge->fAbove.fX);
+ } else if (edge->fTangent.fY < fTangent.fY) {
+ b1 = (fTangent.fY - fAbove.fY) * (fTangent.fX - edge->fTangent.fX);
+ b2 = (fTangent.fY - edge->fTangent.fY) * (fTangent.fX - fAbove.fX);
+ } else {
+ b1 = fTangent.fX;
+ b2 = edge->fTangent.fX;
+ }
+ if (fabs(t1 - t2) > fabs(b1 - b2)) {
+ ulps = UlpsDiff((float) t1, (float) t2);
+ } else {
+ ulps = UlpsDiff((float) b1, (float) b2);
+ }
+#if DEBUG_ADJUST_COINCIDENT
+ SkDebugf("%s this=%d edge=%d ulps=%d\n", __FUNCTION__, ID(), edge->ID(),
+ ulps);
+#endif
+ if (ulps < 0 || ulps > 32) {
+ return false;
+ }
+ if (fVerb == SkPath::kLine_Verb && edge->fVerb == SkPath::kLine_Verb) {
+ return true;
+ }
+ if (fVerb != SkPath::kLine_Verb && edge->fVerb != SkPath::kLine_Verb) {
+ return false;
+ }
+
+ double ts[2];
+ bool isLine = true;
+ bool curveQuad = true;
+ if (fVerb == SkPath::kCubic_Verb) {
+ ts[0] = (fTAbove * 2 + fTBelow) / 3;
+ ts[1] = (fTAbove + fTBelow * 2) / 3;
+ curveQuad = isLine = false;
+ } else if (edge->fVerb == SkPath::kCubic_Verb) {
+ ts[0] = (edge->fTAbove * 2 + edge->fTBelow) / 3;
+ ts[1] = (edge->fTAbove + edge->fTBelow * 2) / 3;
+ curveQuad = false;
+ } else if (fVerb == SkPath::kQuad_Verb) {
+ ts[0] = fTAbove;
+ ts[1] = (fTAbove + fTBelow) / 2;
+ isLine = false;
+ } else {
+ SkASSERT(edge->fVerb == SkPath::kQuad_Verb);
+ ts[0] = edge->fTAbove;
+ ts[1] = (edge->fTAbove + edge->fTBelow) / 2;
+ }
+ const SkPoint* curvePts = isLine ? edge->lastPoints() : lastPoints();
+ const ActiveEdge* lineEdge = isLine ? this : edge;
+ SkPoint curveSample[2];
+ for (int index = 0; index < 2; ++index) {
+ if (curveQuad) {
+ QuadXYAtT(curvePts, ts[index], &curveSample[index]);
+ } else {
+ CubicXYAtT(curvePts, ts[index], &curveSample[index]);
+ }
+ }
+ return IsCoincident(curveSample, lineEdge->fAbove, lineEdge->fBelow);
+ }
+
+ double nextT() const {
+ SkASSERT(fTIndex <= fTs->count() - fExplicitTs);
+ return t(fTIndex + 1);
+ }
+
+ double t() const {
+ return t(fTIndex);
+ }
+
+ double t(int tIndex) const {
+ if (fExplicitTs) {
+ SkASSERT(tIndex < fTs->count());
+ return (*fTs)[tIndex];
+ }
+ if (tIndex == 0) {
+ return 0;
+ }
+ if (tIndex > fTs->count()) {
+ return 1;
+ }
+ return (*fTs)[tIndex - 1];
+ }
+
+ // FIXME: debugging only
+ int ID() const {
+ return fWorkEdge.fEdge->fID;
+ }
+
+private:
+ // utility used only by swapUnordered
+ void extractAboveBelow(ActiveEdge& extracted) const {
+ SkPoint curve[4];
+ switch (fVerb) {
+ case SkPath::kLine_Verb:
+ extracted.fAbove = fAbove;
+ extracted.fTangent = fTangent;
+ return;
+ case SkPath::kQuad_Verb:
+ QuadSubDivide(lastPoints(), fTAbove, fTBelow, curve);
+ break;
+ case SkPath::kCubic_Verb:
+ CubicSubDivide(lastPoints(), fTAbove, fTBelow, curve);
+ break;
+ default:
+ SkASSERT(0);
+ }
+ extracted.fAbove = curve[0];
+ extracted.fTangent = curve[1];
+ }
+
+public:
+ WorkEdge fWorkEdge;
+ const SkTDArray<double>* fTs;
+ SkPoint fAbove;
+ SkPoint fTangent;
+ SkPoint fBelow;
+ double fTAbove; // OPTIMIZATION: only required if edge has quads or cubics
+ double fTBelow;
+ SkScalar fYBottom;
+ int fCoincident;
+ int fTIndex;
+ SkPath::Verb fVerb;
+ bool fSkip; // OPTIMIZATION: use bitfields?
+ bool fCloseCall;
+ bool fDone;
+ bool fFixBelow;
+ bool fExplicitTs;
+};
+
+static void addToActive(SkTDArray<ActiveEdge>& activeEdges, const InEdge* edge) {
+ size_t count = activeEdges.count();
+ for (size_t index = 0; index < count; ++index) {
+ if (edge == activeEdges[index].fWorkEdge.fEdge) {
+ return;
+ }
+ }
+ ActiveEdge* active = activeEdges.append();
+ active->init(edge);
+}
+
+// Find any intersections in the range of active edges. A pair of edges, on
+// either side of another edge, may change the winding contribution for part of
+// the edge.
+// Keep horizontal edges just for
+// the purpose of computing when edges change their winding contribution, since
+// this is essentially computing the horizontal intersection.
+static void addBottomT(InEdge** currentPtr, InEdge** lastPtr,
+ HorizontalEdge** horizontal) {
+ InEdge** testPtr = currentPtr - 1;
+ HorizontalEdge* horzEdge = *horizontal;
+ SkScalar left = horzEdge->fLeft;
+ SkScalar bottom = horzEdge->fY;
+ while (++testPtr != lastPtr) {
+ InEdge* test = *testPtr;
+ if (test->fBounds.fBottom <= bottom || test->fBounds.fRight <= left) {
+ continue;
+ }
+ WorkEdge wt;
+ wt.init(test);
+ do {
+ HorizontalEdge** sorted = horizontal;
+ horzEdge = *sorted;
+ do {
+ double wtTs[4];
+ int pts;
+ uint8_t verb = wt.verb();
+ switch (verb) {
+ case SkPath::kLine_Verb:
+ pts = LineIntersect(wt.fPts, horzEdge->fLeft,
+ horzEdge->fRight, horzEdge->fY, wtTs);
+ break;
+ case SkPath::kQuad_Verb:
+ pts = QuadIntersect(wt.fPts, horzEdge->fLeft,
+ horzEdge->fRight, horzEdge->fY, wtTs);
+ break;
+ case SkPath::kCubic_Verb:
+ pts = CubicIntersect(wt.fPts, horzEdge->fLeft,
+ horzEdge->fRight, horzEdge->fY, wtTs);
+ break;
+ }
+ if (pts) {
+#if DEBUG_ADD_BOTTOM_TS
+ for (int x = 0; x < pts; ++x) {
+ SkDebugf("%s y=%g wtTs[0]=%g (%g,%g", __FUNCTION__,
+ horzEdge->fY, wtTs[x], wt.fPts[0].fX, wt.fPts[0].fY);
+ for (int y = 0; y < verb; ++y) {
+ SkDebugf(" %g,%g", wt.fPts[y + 1].fX, wt.fPts[y + 1].fY));
+ }
+ SkDebugf(")\n");
+ }
+ if (pts > verb) {
+ SkASSERT(0); // FIXME ? should this work?
+ SkDebugf("%s wtTs[1]=%g\n", __FUNCTION__, wtTs[1]);
+ }
+#endif
+ test->add(wtTs, pts, wt.verbIndex());
+ }
+ horzEdge = *++sorted;
+ } while (horzEdge->fY == bottom
+ && horzEdge->fLeft <= test->fBounds.fRight);
+ } while (wt.advance());
+ }
+}
+
+#if DEBUG_ADD_INTERSECTING_TS
+static void debugShowLineIntersection(int pts, const WorkEdge& wt,
+ const WorkEdge& wn, const double wtTs[2], const double wnTs[2]) {
+ if (!pts) {
+ return;
+ }
+ SkPoint wtOutPt, wnOutPt;
+ LineXYAtT(wt.fPts, wtTs[0], &wtOutPt);
+ LineXYAtT(wn.fPts, wnTs[0], &wnOutPt);
+ SkDebugf("%s wtTs[0]=%g (%g,%g, %g,%g) (%g,%g)\n",
+ __FUNCTION__,
+ wtTs[0], wt.fPts[0].fX, wt.fPts[0].fY,
+ wt.fPts[1].fX, wt.fPts[1].fY, wtOutPt.fX, wtOutPt.fY);
+ if (pts == 2) {
+ SkDebugf("%s wtTs[1]=%g\n", __FUNCTION__, wtTs[1]);
+ }
+ SkDebugf("%s wnTs[0]=%g (%g,%g, %g,%g) (%g,%g)\n",
+ __FUNCTION__,
+ wnTs[0], wn.fPts[0].fX, wn.fPts[0].fY,
+ wn.fPts[1].fX, wn.fPts[1].fY, wnOutPt.fX, wnOutPt.fY);
+ if (pts == 2) {
+ SkDebugf("%s wnTs[1]=%g\n", __FUNCTION__, wnTs[1]);
+ }
+}
+#else
+static void debugShowLineIntersection(int , const WorkEdge& ,
+ const WorkEdge& , const double [2], const double [2]) {
+}
+#endif
+
+static void addIntersectingTs(InEdge** currentPtr, InEdge** lastPtr) {
+ InEdge** testPtr = currentPtr - 1;
+ // FIXME: lastPtr should be past the point of interest, so
+ // test below should be lastPtr - 2
+ // that breaks testSimplifyTriangle22, so further investigation is needed
+ while (++testPtr != lastPtr - 1) {
+ InEdge* test = *testPtr;
+ InEdge** nextPtr = testPtr;
+ do {
+ InEdge* next = *++nextPtr;
+ // FIXME: this compares against the sentinel sometimes
+ // OPTIMIZATION: this may never be needed since this gets called
+ // in two passes now. Verify that double hits are appropriate.
+ if (test->cached(next)) {
+ continue;
+ }
+ if (!Bounds::Intersects(test->fBounds, next->fBounds)) {
+ continue;
+ }
+ WorkEdge wt, wn;
+ wt.init(test);
+ wn.init(next);
+ do {
+ int pts;
+ Intersections ts;
+ bool swap = false;
+ switch (wt.verb()) {
+ case SkPath::kLine_Verb:
+ switch (wn.verb()) {
+ case SkPath::kLine_Verb: {
+ pts = LineIntersect(wt.fPts, wn.fPts, ts);
+ debugShowLineIntersection(pts, wt, wn,
+ ts.fT[0], ts.fT[1]);
+ break;
+ }
+ case SkPath::kQuad_Verb: {
+ swap = true;
+ pts = QuadLineIntersect(wn.fPts, wt.fPts, ts);
+ break;
+ }
+ case SkPath::kCubic_Verb: {
+ swap = true;
+ pts = CubicLineIntersect(wn.fPts, wt.fPts, ts);
+ break;
+ }
+ default:
+ SkASSERT(0);
+ }
+ break;
+ case SkPath::kQuad_Verb:
+ switch (wn.verb()) {
+ case SkPath::kLine_Verb: {
+ pts = QuadLineIntersect(wt.fPts, wn.fPts, ts);
+ break;
+ }
+ case SkPath::kQuad_Verb: {
+ pts = QuadIntersect(wt.fPts, wn.fPts, ts);
+ break;
+ }
+ case SkPath::kCubic_Verb: {
+ // FIXME: promote quad to cubic
+ pts = CubicIntersect(wt.fPts, wn.fPts, ts);
+ break;
+ }
+ default:
+ SkASSERT(0);
+ }
+ break;
+ case SkPath::kCubic_Verb:
+ switch (wn.verb()) {
+ case SkPath::kLine_Verb: {
+ pts = CubicLineIntersect(wt.fPts, wn.fPts, ts);
+ break;
+ }
+ case SkPath::kQuad_Verb: {
+ // FIXME: promote quad to cubic
+ pts = CubicIntersect(wt.fPts, wn.fPts, ts);
+ break;
+ }
+ case SkPath::kCubic_Verb: {
+ pts = CubicIntersect(wt.fPts, wn.fPts, ts);
+ break;
+ }
+ default:
+ SkASSERT(0);
+ }
+ break;
+ default:
+ SkASSERT(0);
+ }
+ test->add(ts.fT[swap], pts, wt.verbIndex());
+ next->add(ts.fT[!swap], pts, wn.verbIndex());
+ } while (wt.bottom() <= wn.bottom() ? wt.advance() : wn.advance());
+ } while (nextPtr != lastPtr);
+ }
+}
+
+static InEdge** advanceEdges(SkTDArray<ActiveEdge>* activeEdges,
+ InEdge** currentPtr, InEdge** lastPtr, SkScalar y) {
+ InEdge** testPtr = currentPtr - 1;
+ while (++testPtr != lastPtr) {
+ if ((*testPtr)->fBounds.fBottom > y) {
+ continue;
+ }
+ if (activeEdges) {
+ InEdge* test = *testPtr;
+ ActiveEdge* activePtr = activeEdges->begin() - 1;
+ ActiveEdge* lastActive = activeEdges->end();
+ while (++activePtr != lastActive) {
+ if (activePtr->fWorkEdge.fEdge == test) {
+ activeEdges->remove(activePtr - activeEdges->begin());
+ break;
+ }
+ }
+ }
+ if (testPtr == currentPtr) {
+ ++currentPtr;
+ }
+ }
+ return currentPtr;
+}
+
+// OPTIMIZE: inline?
+static HorizontalEdge** advanceHorizontal(HorizontalEdge** edge, SkScalar y) {
+ while ((*edge)->fY < y) {
+ ++edge;
+ }
+ return edge;
+}
+
+// compute bottom taking into account any intersected edges
+static SkScalar computeInterceptBottom(SkTDArray<ActiveEdge>& activeEdges,
+ SkScalar y, SkScalar bottom) {
+ ActiveEdge* activePtr = activeEdges.begin() - 1;
+ ActiveEdge* lastActive = activeEdges.end();
+ while (++activePtr != lastActive) {
+ const InEdge* test = activePtr->fWorkEdge.fEdge;
+ if (!test->fContainsIntercepts) {
+ continue;
+ }
+ WorkEdge wt;
+ wt.init(test);
+ do {
+ const Intercepts& intercepts = test->fIntercepts[wt.verbIndex()];
+ if (intercepts.fTopIntercepts > 1) {
+ SkScalar yTop = wt.fPts[0].fY;
+ if (yTop > y && bottom > yTop) {
+ bottom = yTop;
+ }
+ }
+ if (intercepts.fBottomIntercepts > 1) {
+ SkScalar yBottom = wt.fPts[wt.verb()].fY;
+ if (yBottom > y && bottom > yBottom) {
+ bottom = yBottom;
+ }
+ }
+ const SkTDArray<double>& fTs = intercepts.fTs;
+ size_t count = fTs.count();
+ for (size_t index = 0; index < count; ++index) {
+ SkScalar yIntercept;
+ switch (wt.verb()) {
+ case SkPath::kLine_Verb: {
+ yIntercept = LineYAtT(wt.fPts, fTs[index]);
+ break;
+ }
+ case SkPath::kQuad_Verb: {
+ yIntercept = QuadYAtT(wt.fPts, fTs[index]);
+ break;
+ }
+ case SkPath::kCubic_Verb: {
+ yIntercept = CubicYAtT(wt.fPts, fTs[index]);
+ break;
+ }
+ default:
+ SkASSERT(0); // should never get here
+ }
+ if (yIntercept > y && bottom > yIntercept) {
+ bottom = yIntercept;
+ }
+ }
+ } while (wt.advance());
+ }
+#if DEBUG_BOTTOM
+ SkDebugf("%s bottom=%1.9g\n", __FUNCTION__, bottom);
+#endif
+ return bottom;
+}
+
+static SkScalar findBottom(InEdge** currentPtr,
+ InEdge** edgeListEnd, SkTDArray<ActiveEdge>* activeEdges, SkScalar y,
+ bool /*asFill*/, InEdge**& testPtr) {
+ InEdge* current = *currentPtr;
+ SkScalar bottom = current->fBounds.fBottom;
+
+ // find the list of edges that cross y
+ InEdge* test = *testPtr;
+ while (testPtr != edgeListEnd) {
+ SkScalar testTop = test->fBounds.fTop;
+ if (bottom <= testTop) {
+ break;
+ }
+ SkScalar testBottom = test->fBounds.fBottom;
+ // OPTIMIZATION: Shortening the bottom is only interesting when filling
+ // and when the edge is to the left of a longer edge. If it's a framing
+ // edge, or part of the right, it won't effect the longer edges.
+ if (testTop > y) {
+ bottom = testTop;
+ break;
+ }
+ if (y < testBottom) {
+ if (bottom > testBottom) {
+ bottom = testBottom;
+ }
+ if (activeEdges) {
+ addToActive(*activeEdges, test);
+ }
+ }
+ test = *++testPtr;
+ }
+#if DEBUG_BOTTOM
+ SkDebugf("%s %d bottom=%1.9g\n", __FUNCTION__, activeEdges ? 2 : 1, bottom);
+#endif
+ return bottom;
+}
+
+static void makeEdgeList(SkTArray<InEdge>& edges, InEdge& edgeSentinel,
+ SkTDArray<InEdge*>& edgeList) {
+ size_t edgeCount = edges.count();
+ if (edgeCount == 0) {
+ return;
+ }
+ int id = 0;
+ for (size_t index = 0; index < edgeCount; ++index) {
+ InEdge& edge = edges[index];
+ if (!edge.fWinding) {
+ continue;
+ }
+ edge.fID = ++id;
+ *edgeList.append() = &edge;
+ }
+ *edgeList.append() = &edgeSentinel;
+ QSort<InEdge>(edgeList.begin(), edgeList.end() - 1);
+}
+
+static void makeHorizontalList(SkTDArray<HorizontalEdge>& edges,
+ HorizontalEdge& edgeSentinel, SkTDArray<HorizontalEdge*>& edgeList) {
+ size_t edgeCount = edges.count();
+ if (edgeCount == 0) {
+ return;
+ }
+ for (size_t index = 0; index < edgeCount; ++index) {
+ *edgeList.append() = &edges[index];
+ }
+ edgeSentinel.fLeft = edgeSentinel.fRight = edgeSentinel.fY = SK_ScalarMax;
+ *edgeList.append() = &edgeSentinel;
+ QSort<HorizontalEdge>(edgeList.begin(), edgeList.end() - 1);
+}
+
+static void skipCoincidence(int lastWinding, int winding, int windingMask,
+ ActiveEdge* activePtr, ActiveEdge* firstCoincident) {
+ if (((lastWinding & windingMask) == 0) ^ ((winding & windingMask) != 0)) {
+ return;
+ }
+ // FIXME: ? shouldn't this be if (lastWinding & windingMask) ?
+ if (lastWinding) {
+#if DEBUG_ADJUST_COINCIDENT
+ SkDebugf("%s edge=%d 1 set skip=false\n", __FUNCTION__, activePtr->ID());
+#endif
+ activePtr->fSkip = false;
+ } else {
+#if DEBUG_ADJUST_COINCIDENT
+ SkDebugf("%s edge=%d 2 set skip=false\n", __FUNCTION__, firstCoincident->ID());
+#endif
+ firstCoincident->fSkip = false;
+ }
+}
+
+static void sortHorizontal(SkTDArray<ActiveEdge>& activeEdges,
+ SkTDArray<ActiveEdge*>& edgeList, SkScalar y) {
+ size_t edgeCount = activeEdges.count();
+ if (edgeCount == 0) {
+ return;
+ }
+#if DEBUG_SORT_HORIZONTAL
+ const int tab = 3; // FIXME: debugging only
+ SkDebugf("%s y=%1.9g\n", __FUNCTION__, y);
+#endif
+ size_t index;
+ for (index = 0; index < edgeCount; ++index) {
+ ActiveEdge& activeEdge = activeEdges[index];
+ do {
+ activeEdge.calcLeft(y);
+ // skip segments that don't span y
+ if (activeEdge.fAbove != activeEdge.fBelow) {
+ break;
+ }
+ if (activeEdge.fDone) {
+#if DEBUG_SORT_HORIZONTAL
+ SkDebugf("%*s edge=%d done\n", tab, "", activeEdge.ID());
+#endif
+ goto nextEdge;
+ }
+#if DEBUG_SORT_HORIZONTAL
+ SkDebugf("%*s edge=%d above==below\n", tab, "", activeEdge.ID());
+#endif
+ } while (activeEdge.advanceT() || activeEdge.advance());
+#if DEBUG_SORT_HORIZONTAL
+ SkDebugf("%*s edge=%d above=(%1.9g,%1.9g) (%1.9g) below=(%1.9g,%1.9g)"
+ " (%1.9g)\n", tab, "", activeEdge.ID(),
+ activeEdge.fAbove.fX, activeEdge.fAbove.fY, activeEdge.fTAbove,
+ activeEdge.fBelow.fX, activeEdge.fBelow.fY, activeEdge.fTBelow);
+#endif
+ activeEdge.fSkip = activeEdge.fCloseCall = activeEdge.fFixBelow = false;
+ *edgeList.append() = &activeEdge;
+nextEdge:
+ ;
+ }
+ QSort<ActiveEdge>(edgeList.begin(), edgeList.end() - 1);
+}
+
+// remove coincident edges
+// OPTIMIZE: remove edges? This is tricky because the current logic expects
+// the winding count to be maintained while skipping coincident edges. In
+// addition to removing the coincident edges, the remaining edges would need
+// to have a different winding value, possibly different per intercept span.
+static SkScalar adjustCoincident(SkTDArray<ActiveEdge*>& edgeList,
+ int windingMask, SkScalar y, SkScalar bottom, OutEdgeBuilder& outBuilder)
+{
+#if DEBUG_ADJUST_COINCIDENT
+ SkDebugf("%s y=%1.9g bottom=%1.9g\n", __FUNCTION__, y, bottom);
+#endif
+ size_t edgeCount = edgeList.count();
+ if (edgeCount == 0) {
+ return bottom;
+ }
+ ActiveEdge* activePtr, * nextPtr = edgeList[0];
+ size_t index;
+ bool foundCoincident = false;
+ size_t firstIndex = 0;
+ for (index = 1; index < edgeCount; ++index) {
+ activePtr = nextPtr;
+ nextPtr = edgeList[index];
+ if (firstIndex != index - 1 && activePtr->fVerb > SkPath::kLine_Verb
+ && nextPtr->fVerb == SkPath::kLine_Verb
+ && activePtr->isUnordered(nextPtr)) {
+ // swap the line with the curve
+ // back up to the previous edge and retest
+ SkTSwap<ActiveEdge*>(edgeList[index - 1], edgeList[index]);
+ SkASSERT(index > 1);
+ index -= 2;
+ nextPtr = edgeList[index];
+ continue;
+ }
+ bool closeCall = false;
+ activePtr->fCoincident = firstIndex;
+ if (activePtr->isCoincidentWith(nextPtr)
+ || (closeCall = activePtr->tooCloseToCall(nextPtr))) {
+ activePtr->fSkip = nextPtr->fSkip = foundCoincident = true;
+ activePtr->fCloseCall = nextPtr->fCloseCall = closeCall;
+ } else if (activePtr->isUnordered(nextPtr)) {
+ foundCoincident = true;
+ } else {
+ firstIndex = index;
+ }
+ }
+ nextPtr->fCoincident = firstIndex;
+ if (!foundCoincident) {
+ return bottom;
+ }
+ int winding = 0;
+ nextPtr = edgeList[0];
+ for (index = 1; index < edgeCount; ++index) {
+ int priorWinding = winding;
+ winding += activePtr->fWorkEdge.winding();
+ activePtr = nextPtr;
+ nextPtr = edgeList[index];
+ SkASSERT(activePtr == edgeList[index - 1]);
+ SkASSERT(nextPtr == edgeList[index]);
+ if (activePtr->fCoincident != nextPtr->fCoincident) {
+ continue;
+ }
+ // the coincident edges may not have been sorted above -- advance
+ // the edges and resort if needed
+ // OPTIMIZE: if sorting is done incrementally as new edges are added
+ // and not all at once as is done here, fold this test into the
+ // current less than test.
+ while ((!activePtr->fSkip || !nextPtr->fSkip)
+ && activePtr->fCoincident == nextPtr->fCoincident) {
+ if (activePtr->swapUnordered(nextPtr, bottom)) {
+ winding -= activePtr->fWorkEdge.winding();
+ SkASSERT(activePtr == edgeList[index - 1]);
+ SkASSERT(nextPtr == edgeList[index]);
+ SkTSwap<ActiveEdge*>(edgeList[index - 1], edgeList[index]);
+ if (--index == 0) {
+ winding += activePtr->fWorkEdge.winding();
+ break;
+ }
+ // back up one
+ activePtr = edgeList[index - 1];
+ continue;
+ }
+ SkASSERT(activePtr == edgeList[index - 1]);
+ SkASSERT(nextPtr == edgeList[index]);
+ break;
+ }
+ if (activePtr->fSkip && nextPtr->fSkip) {
+ if (activePtr->fCloseCall ? activePtr->swapClose(nextPtr,
+ priorWinding, winding, windingMask)
+ : activePtr->swapCoincident(nextPtr, bottom)) {
+ winding -= activePtr->fWorkEdge.winding();
+ SkASSERT(activePtr == edgeList[index - 1]);
+ SkASSERT(nextPtr == edgeList[index]);
+ SkTSwap<ActiveEdge*>(edgeList[index - 1], edgeList[index]);
+ SkTSwap<ActiveEdge*>(activePtr, nextPtr);
+ winding += activePtr->fWorkEdge.winding();
+ SkASSERT(activePtr == edgeList[index - 1]);
+ SkASSERT(nextPtr == edgeList[index]);
+ }
+ }
+ }
+ int firstCoincidentWinding = 0;
+ ActiveEdge* firstCoincident = NULL;
+ winding = 0;
+ activePtr = edgeList[0];
+ for (index = 1; index < edgeCount; ++index) {
+ int priorWinding = winding;
+ winding += activePtr->fWorkEdge.winding();
+ nextPtr = edgeList[index];
+ if (activePtr->fSkip && nextPtr->fSkip
+ && activePtr->fCoincident == nextPtr->fCoincident) {
+ if (!firstCoincident) {
+ firstCoincident = activePtr;
+ firstCoincidentWinding = priorWinding;
+ }
+ if (activePtr->fCloseCall) {
+ // If one of the edges has already been added to out as a non
+ // coincident edge, trim it back to the top of this span
+ if (outBuilder.trimLine(y, activePtr->ID())) {
+ activePtr->addTatYAbove(y);
+ #if DEBUG_ADJUST_COINCIDENT
+ SkDebugf("%s 1 edge=%d y=%1.9g (was fYBottom=%1.9g)\n",
+ __FUNCTION__, activePtr->ID(), y, activePtr->fYBottom);
+ #endif
+ activePtr->fYBottom = y;
+ }
+ if (outBuilder.trimLine(y, nextPtr->ID())) {
+ nextPtr->addTatYAbove(y);
+ #if DEBUG_ADJUST_COINCIDENT
+ SkDebugf("%s 2 edge=%d y=%1.9g (was fYBottom=%1.9g)\n",
+ __FUNCTION__, nextPtr->ID(), y, nextPtr->fYBottom);
+ #endif
+ nextPtr->fYBottom = y;
+ }
+ // add missing t values so edges can be the same length
+ SkScalar testY = activePtr->fBelow.fY;
+ nextPtr->addTatYBelow(testY);
+ if (bottom > testY && testY > y) {
+ #if DEBUG_ADJUST_COINCIDENT
+ SkDebugf("%s 3 edge=%d bottom=%1.9g (was bottom=%1.9g)\n",
+ __FUNCTION__, activePtr->ID(), testY, bottom);
+ #endif
+ bottom = testY;
+ }
+ testY = nextPtr->fBelow.fY;
+ activePtr->addTatYBelow(testY);
+ if (bottom > testY && testY > y) {
+ #if DEBUG_ADJUST_COINCIDENT
+ SkDebugf("%s 4 edge=%d bottom=%1.9g (was bottom=%1.9g)\n",
+ __FUNCTION__, nextPtr->ID(), testY, bottom);
+ #endif
+ bottom = testY;
+ }
+ }
+ } else if (firstCoincident) {
+ skipCoincidence(firstCoincidentWinding, winding, windingMask,
+ activePtr, firstCoincident);
+ firstCoincident = NULL;
+ }
+ activePtr = nextPtr;
+ }
+ if (firstCoincident) {
+ winding += activePtr->fWorkEdge.winding();
+ skipCoincidence(firstCoincidentWinding, winding, windingMask, activePtr,
+ firstCoincident);
+ }
+ // fix up the bottom for close call edges. OPTIMIZATION: maybe this could
+ // be in the loop above, but moved here since loop above reads fBelow and
+ // it felt unsafe to write it in that loop
+ for (index = 0; index < edgeCount; ++index) {
+ (edgeList[index])->fixBelow();
+ }
+ return bottom;
+}
+
+// stitch edge and t range that satisfies operation
+static void stitchEdge(SkTDArray<ActiveEdge*>& edgeList, SkScalar
+#if DEBUG_STITCH_EDGE
+y
+#endif
+,
+ SkScalar bottom, int windingMask, bool fill, OutEdgeBuilder& outBuilder) {
+ int winding = 0;
+ ActiveEdge** activeHandle = edgeList.begin() - 1;
+ ActiveEdge** lastActive = edgeList.end();
+#if DEBUG_STITCH_EDGE
+ const int tab = 7; // FIXME: debugging only
+ SkDebugf("%s y=%1.9g bottom=%1.9g\n", __FUNCTION__, y, bottom);
+#endif
+ while (++activeHandle != lastActive) {
+ ActiveEdge* activePtr = *activeHandle;
+ const WorkEdge& wt = activePtr->fWorkEdge;
+ int lastWinding = winding;
+ winding += wt.winding();
+#if DEBUG_STITCH_EDGE
+ SkDebugf("%*s edge=%d lastWinding=%d winding=%d skip=%d close=%d"
+ " above=%1.9g below=%1.9g\n",
+ tab-4, "", activePtr->ID(), lastWinding,
+ winding, activePtr->fSkip, activePtr->fCloseCall,
+ activePtr->fTAbove, activePtr->fTBelow);
+#endif
+ if (activePtr->done(bottom)) {
+#if DEBUG_STITCH_EDGE
+ SkDebugf("%*s fDone=%d || fYBottom=%1.9g >= bottom\n", tab, "",
+ activePtr->fDone, activePtr->fYBottom);
+#endif
+ continue;
+ }
+ int opener = (lastWinding & windingMask) == 0;
+ bool closer = (winding & windingMask) == 0;
+ SkASSERT(!opener | !closer);
+ bool inWinding = opener | closer;
+ SkPoint clippedPts[4];
+ const SkPoint* clipped = NULL;
+ bool moreToDo, aboveBottom;
+ do {
+ double currentT = activePtr->t();
+ const SkPoint* points = wt.fPts;
+ double nextT;
+ SkPath::Verb verb = activePtr->fVerb;
+ do {
+ nextT = activePtr->nextT();
+ // FIXME: obtuse: want efficient way to say
+ // !currentT && currentT != 1 || !nextT && nextT != 1
+ if (currentT * nextT != 0 || currentT + nextT != 1) {
+ // OPTIMIZATION: if !inWinding, we only need
+ // clipped[1].fY
+ switch (verb) {
+ case SkPath::kLine_Verb:
+ LineSubDivide(points, currentT, nextT, clippedPts);
+ break;
+ case SkPath::kQuad_Verb:
+ QuadSubDivide(points, currentT, nextT, clippedPts);
+ break;
+ case SkPath::kCubic_Verb:
+ CubicSubDivide(points, currentT, nextT, clippedPts);
+ break;
+ default:
+ SkASSERT(0);
+ break;
+ }
+ clipped = clippedPts;
+ } else {
+ clipped = points;
+ }
+ if (inWinding && !activePtr->fSkip && (fill ? clipped[0].fY
+ != clipped[verb].fY : clipped[0] != clipped[verb])) {
+#if DEBUG_STITCH_EDGE
+ SkDebugf("%*s add%s %1.9g,%1.9g %1.9g,%1.9g edge=%d"
+ " v=%d t=(%1.9g,%1.9g)\n", tab, "",
+ kUVerbStr[verb], clipped[0].fX, clipped[0].fY,
+ clipped[verb].fX, clipped[verb].fY,
+ activePtr->ID(),
+ activePtr->fWorkEdge.fVerb
+ - activePtr->fWorkEdge.fEdge->fVerbs.begin(),
+ currentT, nextT);
+#endif
+ outBuilder.addCurve(clipped, (SkPath::Verb) verb,
+ activePtr->fWorkEdge.fEdge->fID,
+ activePtr->fCloseCall);
+ } else {
+#if DEBUG_STITCH_EDGE
+ SkDebugf("%*s skip%s %1.9g,%1.9g %1.9g,%1.9g"
+ " edge=%d v=%d t=(%1.9g,%1.9g)\n", tab, "",
+ kUVerbStr[verb], clipped[0].fX, clipped[0].fY,
+ clipped[verb].fX, clipped[verb].fY,
+ activePtr->ID(),
+ activePtr->fWorkEdge.fVerb
+ - activePtr->fWorkEdge.fEdge->fVerbs.begin(),
+ currentT, nextT);
+#endif
+ }
+ // by advancing fAbove/fBelow, the next call to sortHorizontal
+ // will use these values if they're still valid instead of
+ // recomputing
+ if (clipped[verb].fY > activePtr->fBelow.fY
+ && bottom >= activePtr->fBelow.fY
+ && verb == SkPath::kLine_Verb) {
+ activePtr->fAbove = activePtr->fBelow;
+ activePtr->fBelow = activePtr->fTangent = clipped[verb];
+ activePtr->fTAbove = activePtr->fTBelow < 1
+ ? activePtr->fTBelow : 0;
+ activePtr->fTBelow = nextT;
+ }
+ currentT = nextT;
+ moreToDo = activePtr->advanceT();
+ activePtr->fYBottom = clipped[verb].fY; // was activePtr->fCloseCall ? bottom :
+
+ // clearing the fSkip/fCloseCall bit here means that trailing edges
+ // fall out of sync, if one edge is long and another is a series of short pieces
+ // if fSkip/fCloseCall is set, need to recompute coincidence/too-close-to-call
+ // after advancing
+ // another approach would be to restrict bottom to smaller part of close call
+ // maybe this is already happening with coincidence when intersection is computed,
+ // and needs to be added to the close call computation as well
+ // this is hard to do because that the bottom is important is not known when
+ // the lines are intersected; only when the computation for edge sorting is done
+ // does the need for new bottoms become apparent.
+ // maybe this is good incentive to scrap the current sort and do an insertion
+ // sort that can take this into consideration when the x value is computed
+
+ // FIXME: initialized in sortHorizontal, cleared here as well so
+ // that next edge is not skipped -- but should skipped edges ever
+ // continue? (probably not)
+ aboveBottom = clipped[verb].fY < bottom;
+ if (clipped[0].fY != clipped[verb].fY) {
+ activePtr->fSkip = false;
+ activePtr->fCloseCall = false;
+ aboveBottom &= !activePtr->fCloseCall;
+ }
+#if DEBUG_STITCH_EDGE
+ else {
+ if (activePtr->fSkip || activePtr->fCloseCall) {
+ SkDebugf("%s skip or close == %1.9g\n", __FUNCTION__,
+ clippedPts[0].fY);
+ }
+ }
+#endif
+ } while (moreToDo & aboveBottom);
+ } while ((moreToDo || activePtr->advance()) & aboveBottom);
+ }
+}
+
+#if DEBUG_DUMP
+static void dumpEdgeList(const SkTDArray<InEdge*>& edgeList,
+ const InEdge& edgeSentinel) {
+ InEdge** debugPtr = edgeList.begin();
+ do {
+ (*debugPtr++)->dump();
+ } while (*debugPtr != &edgeSentinel);
+}
+#else
+static void dumpEdgeList(const SkTDArray<InEdge*>& ,
+ const InEdge& ) {
+}
+#endif
+
+void simplify(const SkPath& path, bool asFill, SkPath& simple) {
+ // returns 1 for evenodd, -1 for winding, regardless of inverse-ness
+ int windingMask = (path.getFillType() & 1) ? 1 : -1;
+ simple.reset();
+ simple.setFillType(SkPath::kEvenOdd_FillType);
+ // turn path into list of edges increasing in y
+ // if an edge is a quad or a cubic with a y extrema, note it, but leave it
+ // unbroken. Once we have a list, sort it, then walk the list (walk edges
+ // twice that have y extrema's on top) and detect crossings -- look for raw
+ // bounds that cross over, then tight bounds that cross
+ SkTArray<InEdge> edges;
+ SkTDArray<HorizontalEdge> horizontalEdges;
+ InEdgeBuilder builder(path, asFill, edges, horizontalEdges);
+ SkTDArray<InEdge*> edgeList;
+ InEdge edgeSentinel;
+ edgeSentinel.reset();
+ makeEdgeList(edges, edgeSentinel, edgeList);
+ SkTDArray<HorizontalEdge*> horizontalList;
+ HorizontalEdge horizontalSentinel;
+ makeHorizontalList(horizontalEdges, horizontalSentinel, horizontalList);
+ InEdge** currentPtr = edgeList.begin();
+ if (!currentPtr) {
+ return;
+ }
+ // find all intersections between edges
+// beyond looking for horizontal intercepts, we need to know if any active edges
+// intersect edges below 'bottom', but above the active edge segment.
+// maybe it makes more sense to compute all intercepts before doing anything
+// else, since the intercept list is long-lived, at least in the current design.
+ SkScalar y = (*currentPtr)->fBounds.fTop;
+ HorizontalEdge** currentHorizontal = horizontalList.begin();
+ do {
+ InEdge** lastPtr = currentPtr; // find the edge below the bottom of the first set
+ SkScalar bottom = findBottom(currentPtr, edgeList.end(),
+ NULL, y, asFill, lastPtr);
+ if (lastPtr > currentPtr) {
+ if (currentHorizontal) {
+ if ((*currentHorizontal)->fY < SK_ScalarMax) {
+ addBottomT(currentPtr, lastPtr, currentHorizontal);
+ }
+ currentHorizontal = advanceHorizontal(currentHorizontal, bottom);
+ }
+ addIntersectingTs(currentPtr, lastPtr);
+ }
+ y = bottom;
+ currentPtr = advanceEdges(NULL, currentPtr, lastPtr, y);
+ } while (*currentPtr != &edgeSentinel);
+ // if a quadratic or cubic now has an intermediate T value, see if the Ts
+ // on either side cause the Y values to monotonically increase. If not, split
+ // the curve at the new T.
+
+ // try an alternate approach which does not split curves or stitch edges
+ // (may still need adjustCoincident, though)
+ // the idea is to output non-intersecting contours, then figure out their
+ // respective winding contribution
+ // each contour will need to know whether it is CW or CCW, and then whether
+ // a ray from that contour hits any a contour that contains it. The ray can
+ // move to the left and then arbitrarily move up or down (as long as it never
+ // moves to the right) to find a reference sibling contour or containing
+ // contour. If the contour is part of an intersection, the companion contour
+ // that is part of the intersection can determine the containership.
+ if (builder.containsCurves()) {
+ currentPtr = edgeList.begin();
+ SkTArray<InEdge> splits;
+ do {
+ (*currentPtr)->splitInflectionPts(splits);
+ } while (*++currentPtr != &edgeSentinel);
+ if (splits.count()) {
+ for (int index = 0; index < splits.count(); ++index) {
+ edges.push_back(splits[index]);
+ }
+ edgeList.reset();
+ makeEdgeList(edges, edgeSentinel, edgeList);
+ }
+ }
+ dumpEdgeList(edgeList, edgeSentinel);
+ // walk the sorted edges from top to bottom, computing accumulated winding
+ SkTDArray<ActiveEdge> activeEdges;
+ OutEdgeBuilder outBuilder(asFill);
+ currentPtr = edgeList.begin();
+ y = (*currentPtr)->fBounds.fTop;
+ do {
+ InEdge** lastPtr = currentPtr; // find the edge below the bottom of the first set
+ SkScalar bottom = findBottom(currentPtr, edgeList.end(),
+ &activeEdges, y, asFill, lastPtr);
+ if (lastPtr > currentPtr) {
+ bottom = computeInterceptBottom(activeEdges, y, bottom);
+ SkTDArray<ActiveEdge*> activeEdgeList;
+ sortHorizontal(activeEdges, activeEdgeList, y);
+ bottom = adjustCoincident(activeEdgeList, windingMask, y, bottom,
+ outBuilder);
+ stitchEdge(activeEdgeList, y, bottom, windingMask, asFill, outBuilder);
+ }
+ y = bottom;
+ // OPTIMIZATION: as edges expire, InEdge allocations could be released
+ currentPtr = advanceEdges(&activeEdges, currentPtr, lastPtr, y);
+ } while (*currentPtr != &edgeSentinel);
+ // assemble output path from string of pts, verbs
+ outBuilder.bridge();
+ outBuilder.assemble(simple);
+}
diff --git a/experimental/Intersection/EdgeWalkerRectangles_Test.cpp b/experimental/Intersection/EdgeWalkerRectangles_Test.cpp
new file mode 100644
index 0000000000..31edd74d21
--- /dev/null
+++ b/experimental/Intersection/EdgeWalkerRectangles_Test.cpp
@@ -0,0 +1,470 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "EdgeWalker_Test.h"
+#include "Intersection_Tests.h"
+#include "SkBitmap.h"
+
+static SkBitmap bitmap;
+
+static void testSimplifyCoincidentInner() {
+ SkPath path, out;
+ path.setFillType(SkPath::kWinding_FillType);
+ path.addRect(10, 10, 60, 60, SkPath::kCCW_Direction);
+ path.addRect(20, 20, 50, 50, SkPath::kCW_Direction);
+ path.addRect(20, 30, 40, 40, SkPath::kCW_Direction);
+ testSimplify(path, true, out, bitmap);
+}
+
+static void testSimplifyCoincidentVertical() {
+ SkPath path, out;
+ path.setFillType(SkPath::kWinding_FillType);
+ path.addRect(10, 10, 30, 30);
+ path.addRect(10, 30, 30, 40);
+ simplify(path, true, out);
+ SkRect rect;
+ if (!out.isRect(&rect)) {
+ SkDebugf("%s expected rect\n", __FUNCTION__);
+ }
+ if (rect != SkRect::MakeLTRB(10, 10, 30, 40)) {
+ SkDebugf("%s expected union\n", __FUNCTION__);
+ }
+}
+
+static void testSimplifyCoincidentHorizontal() {
+ SkPath path, out;
+ path.setFillType(SkPath::kWinding_FillType);
+ path.addRect(10, 10, 30, 30);
+ path.addRect(30, 10, 40, 30);
+ simplify(path, true, out);
+ SkRect rect;
+ if (!out.isRect(&rect)) {
+ SkDebugf("%s expected rect\n", __FUNCTION__);
+ }
+ if (rect != SkRect::MakeLTRB(10, 10, 40, 30)) {
+ SkDebugf("%s expected union\n", __FUNCTION__);
+ }
+}
+
+static void testSimplifyMulti() {
+ SkPath path, out;
+ path.setFillType(SkPath::kWinding_FillType);
+ path.addRect(10, 10, 30, 30);
+ path.addRect(20, 20, 40, 40);
+ simplify(path, true, out);
+ SkPath expected;
+ expected.setFillType(SkPath::kEvenOdd_FillType);
+ expected.moveTo(10,10); // two cutout corners
+ expected.lineTo(10,30);
+ expected.lineTo(20,30);
+ expected.lineTo(20,40);
+ expected.lineTo(40,40);
+ expected.lineTo(40,20);
+ expected.lineTo(30,20);
+ expected.lineTo(30,10);
+ expected.lineTo(10,10);
+ expected.close();
+ if (out != expected) {
+ SkDebugf("%s expected equal\n", __FUNCTION__);
+ }
+
+ path = out;
+ path.addRect(30, 10, 40, 20);
+ path.addRect(10, 30, 20, 40);
+ simplify(path, true, out);
+ SkRect rect;
+ if (!out.isRect(&rect)) {
+ SkDebugf("%s expected rect\n", __FUNCTION__);
+ }
+ if (rect != SkRect::MakeLTRB(10, 10, 40, 40)) {
+ SkDebugf("%s expected union\n", __FUNCTION__);
+ }
+
+ path = out;
+ path.addRect(10, 10, 40, 40, SkPath::kCCW_Direction);
+ simplify(path, true, out);
+ if (!out.isEmpty()) {
+ SkDebugf("%s expected empty\n", __FUNCTION__);
+ }
+}
+
+static void testSimplifyAddL() {
+ SkPath path, out;
+ path.moveTo(10,10); // 'L' shape
+ path.lineTo(10,40);
+ path.lineTo(40,40);
+ path.lineTo(40,20);
+ path.lineTo(30,20);
+ path.lineTo(30,10);
+ path.lineTo(10,10);
+ path.close();
+ path.addRect(30, 10, 40, 20); // missing notch of 'L'
+ simplify(path, true, out);
+ SkRect rect;
+ if (!out.isRect(&rect)) {
+ SkDebugf("%s expected rect\n", __FUNCTION__);
+ }
+ if (rect != SkRect::MakeLTRB(10, 10, 40, 40)) {
+ SkDebugf("%s expected union\n", __FUNCTION__);
+ }
+}
+
+static void testSimplifyCoincidentCCW() {
+ SkPath path, out;
+ path.addRect(10, 10, 40, 40, SkPath::kCCW_Direction);
+ path.addRect(10, 10, 40, 40, SkPath::kCCW_Direction);
+ simplify(path, true, out);
+ SkRect rect;
+ if (!out.isRect(&rect)) {
+ SkDebugf("%s expected rect\n", __FUNCTION__);
+ }
+ if (rect != SkRect::MakeLTRB(10, 10, 40, 40)) {
+ SkDebugf("%s expected union\n", __FUNCTION__);
+ }
+}
+
+static void testSimplifyCoincidentCW() {
+ SkPath path, out;
+ path.addRect(10, 10, 40, 40, SkPath::kCCW_Direction);
+ path.addRect(10, 10, 40, 40, SkPath::kCW_Direction);
+ simplify(path, true, out);
+ if (!out.isEmpty()) {
+ SkDebugf("%s expected empty\n", __FUNCTION__);
+ }
+}
+
+static void testSimplifyCorner() {
+ SkPath path, out;
+ path.addRect(10, 10, 20, 20, SkPath::kCCW_Direction);
+ path.addRect(20, 20, 40, 40, SkPath::kCW_Direction);
+ simplify(path, true, out);
+ SkTDArray<SkRect> boundsArray;
+ contourBounds(out, boundsArray);
+ if (boundsArray.count() != 2) {
+ SkDebugf("%s expected 2 contours\n", __FUNCTION__);
+ return;
+ }
+ SkRect one = SkRect::MakeLTRB(10, 10, 20, 20);
+ SkRect two = SkRect::MakeLTRB(20, 20, 40, 40);
+ if ((boundsArray[0] != one && boundsArray[0] != two)
+ || (boundsArray[1] != one && boundsArray[1] != two)) {
+ SkDebugf("%s expected match\n", __FUNCTION__);
+ }
+}
+
+static void testSimplifyDiagonal() {
+ SkRect rect2 = SkRect::MakeXYWH(10, 10, 10, 10);
+ for (size_t outDir = SkPath::kCW_Direction; outDir <= SkPath::kCCW_Direction; ++outDir) {
+ for (size_t inDir = SkPath::kCW_Direction; inDir <= SkPath::kCCW_Direction; ++inDir) {
+ for (int x = 0; x <= 20; x += 20) {
+ for (int y = 0; y <= 20; y += 20) {
+ SkPath path, out;
+ SkRect rect1 = SkRect::MakeXYWH(x, y, 10, 10);
+ path.addRect(rect1, static_cast<SkPath::Direction>(outDir));
+ path.addRect(rect2, static_cast<SkPath::Direction>(inDir));
+ simplify(path, true, out);
+ SkPath::Iter iter(out, false);
+ SkPoint pts[4], lastLine[2];
+ SkPath::Verb verb;
+ SkRect bounds[2];
+ bounds[0].setEmpty();
+ bounds[1].setEmpty();
+ SkRect* boundsPtr = bounds;
+ int count = 0, segments = 0;
+ bool lastLineSet = false;
+ while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+ switch (verb) {
+ case SkPath::kMove_Verb:
+ if (!boundsPtr->isEmpty()) {
+ SkASSERT(boundsPtr == bounds);
+ ++boundsPtr;
+ }
+ boundsPtr->set(pts[0].fX, pts[0].fY, pts[0].fX, pts[0].fY);
+ count = 0;
+ lastLineSet = false;
+ break;
+ case SkPath::kLine_Verb:
+ if (lastLineSet) {
+ SkASSERT((lastLine[1].fX - lastLine[0].fX) *
+ (pts[1].fY - lastLine[0].fY) !=
+ (lastLine[1].fY - lastLine[0].fY) *
+ (pts[1].fX - lastLine[0].fX));
+ }
+ lastLineSet = true;
+ lastLine[0] = pts[0];
+ lastLine[1] = pts[1];
+ count = 1;
+ ++segments;
+ break;
+ case SkPath::kClose_Verb:
+ count = 0;
+ break;
+ default:
+ SkDEBUGFAIL("bad verb");
+ return;
+ }
+ for (int i = 1; i <= count; ++i) {
+ boundsPtr->growToInclude(pts[i].fX, pts[i].fY);
+ }
+ }
+ if (boundsPtr != bounds) {
+ SkASSERT((bounds[0] == rect1 || bounds[1] == rect1)
+ && (bounds[0] == rect2 || bounds[1] == rect2));
+ } else {
+ SkASSERT(segments == 8);
+ }
+ }
+ }
+ }
+ }
+}
+
+static void assertOneContour(const SkPath& out, bool edge, bool extend) {
+ SkPath::Iter iter(out, false);
+ SkPoint pts[4];
+ SkPath::Verb verb;
+ SkRect bounds;
+ bounds.setEmpty();
+ int count = 0;
+ while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+ switch (verb) {
+ case SkPath::kMove_Verb:
+ SkASSERT(count == 0);
+ break;
+ case SkPath::kLine_Verb:
+ SkASSERT(pts[0].fX == pts[1].fX || pts[0].fY == pts[1].fY);
+ ++count;
+ break;
+ case SkPath::kClose_Verb:
+ break;
+ default:
+ SkDEBUGFAIL("bad verb");
+ return;
+ }
+ }
+ SkASSERT(count == (extend ? 4 : edge ? 6 : 8));
+}
+
+static void testSimplifyCoincident() {
+ // outside to inside, outside to right, outside to outside
+ // left to inside, left to right, left to outside
+ // inside to right, inside to outside
+ // repeat above for left, right, bottom
+ SkScalar start[] = { 0, 10, 20 };
+ size_t startCount = sizeof(start) / sizeof(start[0]);
+ SkScalar stop[] = { 30, 40, 50 };
+ size_t stopCount = sizeof(stop) / sizeof(stop[0]);
+ SkRect rect2 = SkRect::MakeXYWH(10, 10, 30, 30);
+ for (size_t outDir = SkPath::kCW_Direction; outDir <= SkPath::kCCW_Direction; ++outDir) {
+ for (size_t inDir = SkPath::kCW_Direction; inDir <= SkPath::kCCW_Direction; ++inDir) {
+ for (size_t startIndex = 0; startIndex < startCount; ++startIndex) {
+ for (size_t stopIndex = 0; stopIndex < stopCount; ++stopIndex) {
+ bool extend = start[startIndex] == rect2.fLeft && stop[stopIndex] == rect2.fRight;
+ bool edge = start[startIndex] == rect2.fLeft || stop[stopIndex] == rect2.fRight;
+ SkRect rect1 = SkRect::MakeLTRB(start[startIndex], 0, stop[stopIndex], 10);
+ SkPath path, out;
+ path.addRect(rect1, static_cast<SkPath::Direction>(outDir));
+ path.addRect(rect2, static_cast<SkPath::Direction>(inDir));
+ simplify(path, true, out);
+ assertOneContour(out, edge, extend);
+
+ path.reset();
+ rect1 = SkRect::MakeLTRB(start[startIndex], 40, stop[stopIndex], 50);
+ path.addRect(rect1, static_cast<SkPath::Direction>(outDir));
+ path.addRect(rect2, static_cast<SkPath::Direction>(inDir));
+ simplify(path, true, out);
+ assertOneContour(out, edge, extend);
+
+ path.reset();
+ rect1 = SkRect::MakeLTRB(0, start[startIndex], 10, stop[stopIndex]);
+ path.addRect(rect1, static_cast<SkPath::Direction>(outDir));
+ path.addRect(rect2, static_cast<SkPath::Direction>(inDir));
+ simplify(path, true, out);
+ assertOneContour(out, edge, extend);
+
+ path.reset();
+ rect1 = SkRect::MakeLTRB(40, start[startIndex], 50, stop[stopIndex]);
+ path.addRect(rect1, static_cast<SkPath::Direction>(outDir));
+ path.addRect(rect2, static_cast<SkPath::Direction>(inDir));
+ simplify(path, true, out);
+ assertOneContour(out, edge, extend);
+ }
+ }
+ }
+ }
+}
+
+static void testSimplifyOverlap() {
+ SkScalar start[] = { 0, 10, 20 };
+ size_t startCount = sizeof(start) / sizeof(start[0]);
+ SkScalar stop[] = { 30, 40, 50 };
+ size_t stopCount = sizeof(stop) / sizeof(stop[0]);
+ SkRect rect2 = SkRect::MakeXYWH(10, 10, 30, 30);
+ for (size_t dir = SkPath::kCW_Direction; dir <= SkPath::kCCW_Direction; ++dir) {
+ for (size_t lefty = 0; lefty < startCount; ++lefty) {
+ for (size_t righty = 0; righty < stopCount; ++righty) {
+ for (size_t toppy = 0; toppy < startCount; ++toppy) {
+ for (size_t botty = 0; botty < stopCount; ++botty) {
+ SkRect rect1 = SkRect::MakeLTRB(start[lefty], start[toppy],
+ stop[righty], stop[botty]);
+ SkPath path, out;
+ path.addRect(rect1, static_cast<SkPath::Direction>(dir));
+ path.addRect(rect2, static_cast<SkPath::Direction>(dir));
+ testSimplify(path, true, out, bitmap);
+ }
+ }
+ }
+ }
+ }
+}
+
+static void testSimplifyOverlapTiny() {
+ SkScalar start[] = { 0, 1, 2 };
+ size_t startCount = sizeof(start) / sizeof(start[0]);
+ SkScalar stop[] = { 3, 4, 5 };
+ size_t stopCount = sizeof(stop) / sizeof(stop[0]);
+ SkRect rect2 = SkRect::MakeXYWH(1, 1, 3, 3);
+ for (size_t dir = SkPath::kCW_Direction; dir <= SkPath::kCCW_Direction; ++dir) {
+ for (size_t lefty = 0; lefty < startCount; ++lefty) {
+ for (size_t righty = 0; righty < stopCount; ++righty) {
+ for (size_t toppy = 0; toppy < startCount; ++toppy) {
+ for (size_t botty = 0; botty < stopCount; ++botty) {
+ SkRect rect1 = SkRect::MakeLTRB(start[lefty], start[toppy],
+ stop[righty], stop[botty]);
+ SkPath path, out;
+ path.addRect(rect1, static_cast<SkPath::Direction>(dir));
+ path.addRect(rect2, static_cast<SkPath::Direction>(dir));
+ simplify(path, true, out);
+ comparePathsTiny(path, out);
+ }
+ }
+ }
+ }
+ }
+}
+
+static void testSimplifyDegenerate() {
+ SkScalar start[] = { 0, 10, 20 };
+ size_t startCount = sizeof(start) / sizeof(start[0]);
+ SkScalar stop[] = { 30, 40, 50 };
+ size_t stopCount = sizeof(stop) / sizeof(stop[0]);
+ SkRect rect2 = SkRect::MakeXYWH(10, 10, 30, 30);
+ for (size_t outDir = SkPath::kCW_Direction; outDir <= SkPath::kCCW_Direction; ++outDir) {
+ for (size_t inDir = SkPath::kCW_Direction; inDir <= SkPath::kCCW_Direction; ++inDir) {
+ for (size_t startIndex = 0; startIndex < startCount; ++startIndex) {
+ for (size_t stopIndex = 0; stopIndex < stopCount; ++stopIndex) {
+ SkRect rect1 = SkRect::MakeLTRB(start[startIndex], 0, stop[stopIndex], 0);
+ SkPath path, out;
+ path.addRect(rect1, static_cast<SkPath::Direction>(outDir));
+ path.addRect(rect2, static_cast<SkPath::Direction>(inDir));
+ simplify(path, true, out);
+ SkRect rect;
+ if (!out.isRect(&rect)) {
+ SkDebugf("%s 1 expected rect\n", __FUNCTION__);
+ }
+ if (rect != rect2) {
+ SkDebugf("%s 1 expected union\n", __FUNCTION__);
+ }
+
+ path.reset();
+ rect1 = SkRect::MakeLTRB(start[startIndex], 40, stop[stopIndex], 40);
+ path.addRect(rect1, static_cast<SkPath::Direction>(outDir));
+ path.addRect(rect2, static_cast<SkPath::Direction>(inDir));
+ simplify(path, true, out);
+ if (!out.isRect(&rect)) {
+ SkDebugf("%s 2 expected rect\n", __FUNCTION__);
+ }
+ if (rect != rect2) {
+ SkDebugf("%s 2 expected union\n", __FUNCTION__);
+ }
+
+ path.reset();
+ rect1 = SkRect::MakeLTRB(0, start[startIndex], 0, stop[stopIndex]);
+ path.addRect(rect1, static_cast<SkPath::Direction>(outDir));
+ path.addRect(rect2, static_cast<SkPath::Direction>(inDir));
+ simplify(path, true, out);
+ if (!out.isRect(&rect)) {
+ SkDebugf("%s 3 expected rect\n", __FUNCTION__);
+ }
+ if (rect != rect2) {
+ SkDebugf("%s 3 expected union\n", __FUNCTION__);
+ }
+
+ path.reset();
+ rect1 = SkRect::MakeLTRB(40, start[startIndex], 40, stop[stopIndex]);
+ path.addRect(rect1, static_cast<SkPath::Direction>(outDir));
+ path.addRect(rect2, static_cast<SkPath::Direction>(inDir));
+ simplify(path, true, out);
+ if (!out.isRect(&rect)) {
+ SkDebugf("%s 4 expected rect\n", __FUNCTION__);
+ }
+ if (rect != rect2) {
+ SkDebugf("%s 4 expected union\n", __FUNCTION__);
+ }
+ }
+ }
+ }
+ }
+}
+
+static void testSimplifyDegenerate1() {
+ SkPath path, out;
+ path.setFillType(SkPath::kWinding_FillType);
+ path.addRect( 0, 0, 0, 30);
+ path.addRect(10, 10, 40, 40);
+ simplify(path, true, out);
+ SkRect rect;
+ if (!out.isRect(&rect)) {
+ SkDebugf("%s expected rect\n", __FUNCTION__);
+ }
+ if (rect != SkRect::MakeLTRB(10, 10, 40, 40)) {
+ SkDebugf("%s expected union\n", __FUNCTION__);
+ }
+}
+
+static void (*simplifyTests[])() = {
+ testSimplifyCoincidentInner,
+ testSimplifyOverlapTiny,
+ testSimplifyDegenerate1,
+ testSimplifyCorner,
+ testSimplifyDegenerate,
+ testSimplifyOverlap,
+ testSimplifyDiagonal,
+ testSimplifyCoincident,
+ testSimplifyCoincidentCW,
+ testSimplifyCoincidentCCW,
+ testSimplifyCoincidentVertical,
+ testSimplifyCoincidentHorizontal,
+ testSimplifyAddL,
+ testSimplifyMulti,
+};
+
+static size_t simplifyTestsCount = sizeof(simplifyTests) / sizeof(simplifyTests[0]);
+
+static void (*firstTest)() = 0;
+
+void SimplifyRectangularPaths_Test() {
+ size_t index = 0;
+ if (firstTest) {
+ while (index < simplifyTestsCount && simplifyTests[index] != firstTest) {
+ ++index;
+ }
+ }
+ for ( ; index < simplifyTestsCount; ++index) {
+ if (simplifyTests[index] == testSimplifyCorner) {
+ // testSimplifyCorner fails because it expects two contours, where
+ // only one is returned. Both results are reasonable, but if two
+ // contours are desirable, or if we provide an option to choose
+ // between longer contours and more contours, turn this back on. For
+ // the moment, testSimplifyDiagonal also checks the test case, and
+ // permits either two rects or one non-crossing poly as valid
+ // unreported results.
+ continue;
+ }
+ (*simplifyTests[index])();
+ }
+}
+
diff --git a/experimental/Intersection/EdgeWalker_Test.h b/experimental/Intersection/EdgeWalker_Test.h
new file mode 100644
index 0000000000..205051ebae
--- /dev/null
+++ b/experimental/Intersection/EdgeWalker_Test.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "ShapeOps.h"
+#include "SkBitmap.h"
+#include "SkStream.h"
+#include <pthread.h>
+
+struct State4;
+
+//extern int comparePaths(const SkPath& one, const SkPath& two);
+extern int comparePaths(const SkPath& one, const SkPath& two, SkBitmap& bitmap);
+extern int comparePaths(const SkPath& one, const SkPath& two, SkBitmap& bitmap,
+ const SkPath& a, const SkPath& b, const ShapeOp shapeOp);
+extern void comparePathsTiny(const SkPath& one, const SkPath& two);
+extern bool drawAsciiPaths(const SkPath& one, const SkPath& two,
+ bool drawPaths);
+extern void showPath(const SkPath& path, const char* str = NULL);
+extern bool testSimplify(const SkPath& path, bool fill, SkPath& out,
+ SkBitmap& bitmap);
+extern bool testSimplifyx(SkPath& path, bool useXor, SkPath& out,
+ State4& state, const char* pathStr);
+extern bool testSimplifyx(const SkPath& path);
+extern bool testShapeOp(const SkPath& a, const SkPath& b, const ShapeOp );
+
+struct State4 {
+ State4();
+ static pthread_mutex_t addQueue;
+ static pthread_cond_t checkQueue;
+ pthread_cond_t initialized;
+ static State4* queue;
+ pthread_t threadID;
+ int index;
+ bool done;
+ bool last;
+ int a;
+ int b;
+ int c;
+ int d; // sometimes 1 if abc_is_a_triangle
+ int testsRun;
+ char filename[256];
+
+ SkBitmap bitmap;
+};
+
+void createThread(State4* statePtr, void* (*test)(void* ));
+int dispatchTest4(void* (*testFun)(void* ), int a, int b, int c, int d);
+void initializeTests(const char* testName, size_t testNameSize);
+void outputProgress(const State4& state, const char* pathStr, SkPath::FillType );
+void outputProgress(const State4& state, const char* pathStr, ShapeOp op);
+void outputToStream(const State4& state, const char* pathStr, const char* pathPrefix,
+ const char* nameSuffix,
+ const char* testFunction, SkWStream& outFile);
+bool runNextTestSet(State4& state);
+int waitForCompletion();
diff --git a/experimental/Intersection/EdgeWalker_TestUtility.cpp b/experimental/Intersection/EdgeWalker_TestUtility.cpp
new file mode 100644
index 0000000000..f07eebe179
--- /dev/null
+++ b/experimental/Intersection/EdgeWalker_TestUtility.cpp
@@ -0,0 +1,765 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "EdgeWalker_Test.h"
+#include "Intersection_Tests.h"
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkPaint.h"
+#include "SkStream.h"
+
+#include <algorithm>
+#include <assert.h>
+#include <errno.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/sysctl.h>
+
+#undef SkASSERT
+#define SkASSERT(cond) while (!(cond)) { sk_throw(); }
+
+static const char marker[] =
+ "</div>\n"
+ "\n"
+ "<script type=\"text/javascript\">\n"
+ "\n"
+ "var testDivs = [\n";
+
+static const char* opStrs[] = {
+ "kDifference_Op",
+ "kIntersect_Op",
+ "kUnion_Op",
+ "kXor_Op",
+};
+
+static const char* opSuffixes[] = {
+ "d",
+ "i",
+ "u",
+ "x",
+};
+
+static const char preferredFilename[] = "/flash/debug/XX.txt";
+static const char backupFilename[] = "../../experimental/Intersection/debugXX.txt";
+
+static bool gShowPath = false;
+static bool gComparePaths = true;
+static bool gShowOutputProgress = false;
+static bool gComparePathsAssert = true;
+static bool gPathStrAssert = true;
+static bool gUsePhysicalFiles = false;
+
+static bool isRectContour(SkPath::Iter& iter, SkRect& rect, SkPath::Direction& direction) {
+ int corners = 0;
+ SkPoint first, last;
+ first.set(0, 0);
+ last.set(0, 0);
+ int firstDirection = 0;
+ int lastDirection = 0;
+ int nextDirection = 0;
+ bool closedOrMoved = false;
+ bool autoClose = false;
+ rect.setEmpty();
+ uint8_t verb;
+ SkPoint data[4];
+ SkTDArray<SkPoint> sides;
+ bool empty = true;
+ while ((verb = iter.next(data)) != SkPath::kDone_Verb && !autoClose) {
+ empty = false;
+ SkPoint* pts = &data[1];
+ switch (verb) {
+ case SkPath::kClose_Verb:
+ pts = &last;
+ autoClose = true;
+ case SkPath::kLine_Verb: {
+ SkScalar left = last.fX;
+ SkScalar top = last.fY;
+ SkScalar right = pts->fX;
+ SkScalar bottom = pts->fY;
+ *sides.append() = *pts;
+ ++pts;
+ if (left != right && top != bottom) {
+ return false; // diagonal
+ }
+ if (left == right && top == bottom) {
+ break; // single point on side OK
+ }
+ nextDirection = (left != right) << 0 |
+ (left < right || top < bottom) << 1;
+ if (0 == corners) {
+ firstDirection = nextDirection;
+ first = last;
+ last = pts[-1];
+ corners = 1;
+ closedOrMoved = false;
+ break;
+ }
+ if (closedOrMoved) {
+ return false; // closed followed by a line
+ }
+ if (autoClose && nextDirection == firstDirection) {
+ break; // colinear with first
+ }
+ closedOrMoved = autoClose;
+ if (lastDirection != nextDirection) {
+ if (++corners > 4) {
+ return false; // too many direction changes
+ }
+ }
+ last = pts[-1];
+ if (lastDirection == nextDirection) {
+ break; // colinear segment
+ }
+ // Possible values for corners are 2, 3, and 4.
+ // When corners == 3, nextDirection opposes firstDirection.
+ // Otherwise, nextDirection at corner 2 opposes corner 4.
+ int turn = firstDirection ^ (corners - 1);
+ int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
+ if ((directionCycle ^ turn) != nextDirection) {
+ return false; // direction didn't follow cycle
+ }
+ break;
+ }
+ case SkPath::kQuad_Verb:
+ case SkPath::kCubic_Verb:
+ return false; // quadratic, cubic not allowed
+ case SkPath::kMove_Verb:
+ last = *pts++;
+ *sides.append() = last;
+ closedOrMoved = true;
+ break;
+ }
+ lastDirection = nextDirection;
+ }
+ // Success if 4 corners and first point equals last
+ bool result = 4 == corners && (first == last || autoClose);
+ if (result) {
+ direction = firstDirection == (lastDirection + 1 & 3) ? SkPath::kCCW_Direction
+ : SkPath::kCW_Direction;
+ rect.set(&sides[0], sides.count());
+ } else {
+ rect.setEmpty();
+ }
+ return !empty;
+}
+
+static void showPathContour(SkPath::Iter& iter, bool skip) {
+ uint8_t verb;
+ SkPoint pts[4];
+ while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+ if (skip) {
+ if (verb == SkPath::kClose_Verb) {
+ return;
+ }
+ }
+ switch (verb) {
+ case SkPath::kMove_Verb:
+ SkDebugf("path.moveTo(%1.9g, %1.9g);\n", pts[0].fX, pts[0].fY);
+ continue;
+ case SkPath::kLine_Verb:
+ SkDebugf("path.lineTo(%1.9g, %1.9g);\n", pts[1].fX, pts[1].fY);
+ break;
+ case SkPath::kQuad_Verb:
+ SkDebugf("path.quadTo(%1.9g, %1.9g, %1.9g, %1.9g);\n",
+ pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
+ break;
+ case SkPath::kCubic_Verb:
+ SkDebugf("path.cubicTo(%1.9g, %1.9g, %1.9g, %1.9g);\n",
+ pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
+ pts[3].fX, pts[3].fY);
+ break;
+ case SkPath::kClose_Verb:
+ SkDebugf("path.close();\n");
+ return;
+ default:
+ SkDEBUGFAIL("bad verb");
+ return;
+ }
+ }
+}
+
+void showPath(const SkPath& path, const char* str) {
+ SkDebugf("%s\n", !str ? "original:" : str);
+ SkPath::Iter iter(path, true);
+ SkTDArray<SkRect> rects;
+ SkTDArray<SkPath::Direction> directions;
+ SkRect rect;
+ SkPath::Direction direction;
+ while (isRectContour(iter, rect, direction)) {
+ *rects.append() = rect;
+ *directions.append() = direction;
+ }
+ iter.setPath(path, true);
+ for (int contour = 0; contour < rects.count(); ++contour) {
+ const SkRect& rect = rects[contour];
+ bool useRect = !rect.isEmpty();
+ showPathContour(iter, useRect);
+ if (useRect) {
+ SkDebugf("path.addRect(%1.9g, %1.9g, %1.9g, %1.9g, %s);\n", rect.fLeft, rect.fTop,
+ rect.fRight, rect.fBottom, directions[contour] == SkPath::kCCW_Direction
+ ? "SkPath::kCCW_Direction" : "SkPath::kCW_Direction");
+ }
+ }
+}
+
+static int pathsDrawTheSame(const SkPath& one, const SkPath& two,
+ SkBitmap& bits, SkPath& scaledOne, SkPath& scaledTwo, int& error2x2) {
+ const int bitWidth = 64;
+ const int bitHeight = 64;
+ if (bits.width() == 0) {
+ bits.setConfig(SkBitmap::kARGB_8888_Config, bitWidth * 2, bitHeight);
+ bits.allocPixels();
+ }
+
+ SkRect larger = one.getBounds();
+ larger.join(two.getBounds());
+ SkScalar largerWidth = larger.width();
+ if (largerWidth < 4) {
+ largerWidth = 4;
+ }
+ SkScalar largerHeight = larger.height();
+ if (largerHeight < 4) {
+ largerHeight = 4;
+ }
+ SkScalar hScale = (bitWidth - 2) / largerWidth;
+ SkScalar vScale = (bitHeight - 2) / largerHeight;
+ SkMatrix scale;
+ scale.reset();
+ scale.preScale(hScale, vScale);
+ one.transform(scale, &scaledOne);
+ two.transform(scale, &scaledTwo);
+ const SkRect& bounds1 = scaledOne.getBounds();
+
+ SkCanvas canvas(bits);
+ canvas.drawColor(SK_ColorWHITE);
+ SkPaint paint;
+ canvas.save();
+ canvas.translate(-bounds1.fLeft + 1, -bounds1.fTop + 1);
+ canvas.drawPath(scaledOne, paint);
+ canvas.restore();
+ canvas.save();
+ canvas.translate(-bounds1.fLeft + 1 + bitWidth, -bounds1.fTop + 1);
+ canvas.drawPath(scaledTwo, paint);
+ canvas.restore();
+ int errors2 = 0;
+ int errors = 0;
+ for (int y = 0; y < bitHeight - 1; ++y) {
+ uint32_t* addr1 = bits.getAddr32(0, y);
+ uint32_t* addr2 = bits.getAddr32(0, y + 1);
+ uint32_t* addr3 = bits.getAddr32(bitWidth, y);
+ uint32_t* addr4 = bits.getAddr32(bitWidth, y + 1);
+ for (int x = 0; x < bitWidth - 1; ++x) {
+ // count 2x2 blocks
+ bool err = addr1[x] != addr3[x];
+ if (err) {
+ errors2 += addr1[x + 1] != addr3[x + 1]
+ && addr2[x] != addr4[x] && addr2[x + 1] != addr4[x + 1];
+ errors++;
+ }
+ }
+ }
+ if (errors2 >= 6 || errors > 160) {
+ SkDebugf("%s errors2=%d errors=%d\n", __FUNCTION__, errors2, errors);
+ }
+ error2x2 = errors2;
+ return errors;
+}
+
+bool drawAsciiPaths(const SkPath& one, const SkPath& two, bool drawPaths) {
+ if (!drawPaths) {
+ return true;
+ }
+ const SkRect& bounds1 = one.getBounds();
+ const SkRect& bounds2 = two.getBounds();
+ SkRect larger = bounds1;
+ larger.join(bounds2);
+ SkBitmap bits;
+ char out[256];
+ int bitWidth = SkScalarCeil(larger.width()) + 2;
+ if (bitWidth * 2 + 1 >= (int) sizeof(out)) {
+ return false;
+ }
+ int bitHeight = SkScalarCeil(larger.height()) + 2;
+ if (bitHeight >= (int) sizeof(out)) {
+ return false;
+ }
+ bits.setConfig(SkBitmap::kARGB_8888_Config, bitWidth * 2, bitHeight);
+ bits.allocPixels();
+ SkCanvas canvas(bits);
+ canvas.drawColor(SK_ColorWHITE);
+ SkPaint paint;
+ canvas.save();
+ canvas.translate(-bounds1.fLeft + 1, -bounds1.fTop + 1);
+ canvas.drawPath(one, paint);
+ canvas.restore();
+ canvas.save();
+ canvas.translate(-bounds1.fLeft + 1 + bitWidth, -bounds1.fTop + 1);
+ canvas.drawPath(two, paint);
+ canvas.restore();
+ for (int y = 0; y < bitHeight; ++y) {
+ uint32_t* addr1 = bits.getAddr32(0, y);
+ int x;
+ char* outPtr = out;
+ for (x = 0; x < bitWidth; ++x) {
+ *outPtr++ = addr1[x] == (uint32_t) -1 ? '_' : 'x';
+ }
+ *outPtr++ = '|';
+ for (x = bitWidth; x < bitWidth * 2; ++x) {
+ *outPtr++ = addr1[x] == (uint32_t) -1 ? '_' : 'x';
+ }
+ *outPtr++ = '\0';
+ SkDebugf("%s\n", out);
+ }
+ return true;
+}
+
+static void showSimplifiedPath(const SkPath& one, const SkPath& two,
+ const SkPath& scaledOne, const SkPath& scaledTwo) {
+ showPath(one, "original:");
+ showPath(two, "simplified:");
+ drawAsciiPaths(scaledOne, scaledTwo, true);
+}
+
+int comparePaths(const SkPath& one, const SkPath& two, SkBitmap& bitmap) {
+ int errors2x2;
+ SkPath scaledOne, scaledTwo;
+ int errors = pathsDrawTheSame(one, two, bitmap, scaledOne, scaledTwo, errors2x2);
+ if (errors2x2 == 0) {
+ return 0;
+ }
+ const int MAX_ERRORS = 8;
+ if (errors2x2 == MAX_ERRORS || errors2x2 == MAX_ERRORS - 1) {
+ showSimplifiedPath(one, two, scaledOne, scaledTwo);
+ }
+ if (errors2x2 > MAX_ERRORS && gComparePathsAssert) {
+ SkDebugf("%s errors=%d\n", __FUNCTION__, errors);
+ showSimplifiedPath(one, two, scaledOne, scaledTwo);
+ SkASSERT(0);
+ }
+ return errors2x2 > MAX_ERRORS ? errors2x2 : 0;
+}
+
+static void showShapeOpPath(const SkPath& one, const SkPath& two, const SkPath& a, const SkPath& b,
+ const SkPath& scaledOne, const SkPath& scaledTwo, const ShapeOp shapeOp) {
+ SkASSERT((unsigned) shapeOp < sizeof(opStrs) / sizeof(opStrs[0]));
+ showPath(a, "minuend:");
+ SkDebugf("op: %s\n", opStrs[shapeOp]);
+ showPath(b, "subtrahend:");
+ showPath(one, "region:");
+ showPath(two, "op result:");
+ drawAsciiPaths(scaledOne, scaledTwo, true);
+}
+
+int comparePaths(const SkPath& one, const SkPath& two, SkBitmap& bitmap,
+ const SkPath& a, const SkPath& b, const ShapeOp shapeOp) {
+ int errors2x2;
+ SkPath scaledOne, scaledTwo;
+ int errors = pathsDrawTheSame(one, two, bitmap, scaledOne, scaledTwo, errors2x2);
+ if (errors2x2 == 0) {
+ return 0;
+ }
+ const int MAX_ERRORS = 8;
+ if (errors2x2 == MAX_ERRORS || errors2x2 == MAX_ERRORS - 1) {
+ showShapeOpPath(one, two, a, b, scaledOne, scaledTwo, shapeOp);
+ }
+ if (errors2x2 > MAX_ERRORS && gComparePathsAssert) {
+ SkDebugf("%s errors=%d\n", __FUNCTION__, errors);
+ showShapeOpPath(one, two, a, b, scaledOne, scaledTwo, shapeOp);
+ SkASSERT(0);
+ }
+ return errors2x2 > MAX_ERRORS ? errors2x2 : 0;
+}
+
+// doesn't work yet
+void comparePathsTiny(const SkPath& one, const SkPath& two) {
+ const SkRect& bounds1 = one.getBounds();
+ const SkRect& bounds2 = two.getBounds();
+ SkRect larger = bounds1;
+ larger.join(bounds2);
+ SkBitmap bits;
+ int bitWidth = SkScalarCeil(larger.width()) + 2;
+ int bitHeight = SkScalarCeil(larger.height()) + 2;
+ bits.setConfig(SkBitmap::kA1_Config, bitWidth * 2, bitHeight);
+ bits.allocPixels();
+ SkCanvas canvas(bits);
+ canvas.drawColor(SK_ColorWHITE);
+ SkPaint paint;
+ canvas.save();
+ canvas.translate(-bounds1.fLeft + 1, -bounds1.fTop + 1);
+ canvas.drawPath(one, paint);
+ canvas.restore();
+ canvas.save();
+ canvas.translate(-bounds2.fLeft + 1, -bounds2.fTop + 1);
+ canvas.drawPath(two, paint);
+ canvas.restore();
+ for (int y = 0; y < bitHeight; ++y) {
+ uint8_t* addr1 = bits.getAddr1(0, y);
+ uint8_t* addr2 = bits.getAddr1(bitWidth, y);
+ for (int x = 0; x < bits.rowBytes(); ++x) {
+ SkASSERT(addr1[x] == addr2[x]);
+ }
+ }
+}
+
+bool testSimplify(const SkPath& path, bool fill, SkPath& out, SkBitmap& bitmap) {
+ if (gShowPath) {
+ showPath(path);
+ }
+ simplify(path, fill, out);
+ if (!gComparePaths) {
+ return true;
+ }
+ return comparePaths(path, out, bitmap) == 0;
+}
+
+bool testSimplifyx(SkPath& path, bool useXor, SkPath& out, State4& state,
+ const char* pathStr) {
+ SkPath::FillType fillType = useXor ? SkPath::kEvenOdd_FillType : SkPath::kWinding_FillType;
+ path.setFillType(fillType);
+ if (gShowPath) {
+ showPath(path);
+ }
+ simplifyx(path, out);
+ if (!gComparePaths) {
+ return true;
+ }
+ int result = comparePaths(path, out, state.bitmap);
+ if (result && gPathStrAssert) {
+ SkDebugf("addTest %s\n", state.filename);
+ char temp[8192];
+ bzero(temp, sizeof(temp));
+ SkMemoryWStream stream(temp, sizeof(temp));
+ const char* pathPrefix = NULL;
+ const char* nameSuffix = NULL;
+ if (fillType == SkPath::kEvenOdd_FillType) {
+ pathPrefix = " path.setFillType(SkPath::kEvenOdd_FillType);\n";
+ nameSuffix = "x";
+ }
+ const char testFunction[] = "testSimplifyx(path);";
+ outputToStream(state, pathStr, pathPrefix, nameSuffix, testFunction, stream);
+ SkDebugf(temp);
+ SkASSERT(0);
+ }
+ return result == 0;
+}
+
+bool testSimplifyx(const SkPath& path) {
+ SkPath out;
+ simplifyx(path, out);
+ SkBitmap bitmap;
+ int result = comparePaths(path, out, bitmap);
+ if (result && gPathStrAssert) {
+ SkASSERT(0);
+ }
+ return result == 0;
+}
+
+bool testShapeOp(const SkPath& a, const SkPath& b, const ShapeOp shapeOp) {
+ SkPath out;
+ operate(a, b, shapeOp, out);
+ SkPath pathOut;
+ SkRegion rgnA, rgnB, openClip, rgnOut;
+ openClip.setRect(-16000, -16000, 16000, 16000);
+ rgnA.setPath(a, openClip);
+ rgnB.setPath(b, openClip);
+ rgnOut.op(rgnA, rgnB, (SkRegion::Op) shapeOp);
+ rgnOut.getBoundaryPath(&pathOut);
+ SkBitmap bitmap;
+ int result = comparePaths(pathOut, out, bitmap, a, b, shapeOp);
+ if (result && gPathStrAssert) {
+ SkASSERT(0);
+ }
+ return result == 0;
+}
+
+const int maxThreadsAllocated = 64;
+static int maxThreads = 1;
+static int threadIndex;
+State4 threadState[maxThreadsAllocated];
+static int testNumber;
+static const char* testName;
+static bool debugThreads = false;
+
+State4* State4::queue = NULL;
+pthread_mutex_t State4::addQueue = PTHREAD_MUTEX_INITIALIZER;
+pthread_cond_t State4::checkQueue = PTHREAD_COND_INITIALIZER;
+
+State4::State4() {
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config, 150 * 2, 100);
+ bitmap.allocPixels();
+}
+
+void createThread(State4* statePtr, void* (*testFun)(void* )) {
+ int threadError = pthread_create(&statePtr->threadID, NULL, testFun,
+ (void*) statePtr);
+ SkASSERT(!threadError);
+}
+
+int dispatchTest4(void* (*testFun)(void* ), int a, int b, int c, int d) {
+ int testsRun = 0;
+ State4* statePtr;
+ if (!gRunTestsInOneThread) {
+ pthread_mutex_lock(&State4::addQueue);
+ if (threadIndex < maxThreads) {
+ statePtr = &threadState[threadIndex];
+ statePtr->testsRun = 0;
+ statePtr->a = a;
+ statePtr->b = b;
+ statePtr->c = c;
+ statePtr->d = d;
+ statePtr->done = false;
+ statePtr->index = threadIndex;
+ statePtr->last = false;
+ if (debugThreads) SkDebugf("%s %d create done=%d last=%d\n", __FUNCTION__,
+ statePtr->index, statePtr->done, statePtr->last);
+ pthread_cond_init(&statePtr->initialized, NULL);
+ ++threadIndex;
+ createThread(statePtr, testFun);
+ } else {
+ while (!State4::queue) {
+ if (debugThreads) SkDebugf("%s checkQueue\n", __FUNCTION__);
+ pthread_cond_wait(&State4::checkQueue, &State4::addQueue);
+ }
+ statePtr = State4::queue;
+ testsRun += statePtr->testsRun;
+ statePtr->testsRun = 0;
+ statePtr->a = a;
+ statePtr->b = b;
+ statePtr->c = c;
+ statePtr->d = d;
+ statePtr->done = false;
+ State4::queue = NULL;
+ for (int index = 0; index < maxThreads; ++index) {
+ if (threadState[index].done) {
+ State4::queue = &threadState[index];
+ }
+ }
+ if (debugThreads) SkDebugf("%s %d init done=%d last=%d queued=%d\n", __FUNCTION__,
+ statePtr->index, statePtr->done, statePtr->last,
+ State4::queue ? State4::queue->index : -1);
+ pthread_cond_signal(&statePtr->initialized);
+ }
+ pthread_mutex_unlock(&State4::addQueue);
+ } else {
+ statePtr = &threadState[0];
+ testsRun += statePtr->testsRun;
+ statePtr->testsRun = 0;
+ statePtr->a = a;
+ statePtr->b = b;
+ statePtr->c = c;
+ statePtr->d = d;
+ statePtr->done = false;
+ statePtr->index = threadIndex;
+ statePtr->last = false;
+ (*testFun)(statePtr);
+ }
+ return testsRun;
+}
+
+void initializeTests(const char* test, size_t testNameSize) {
+ testName = test;
+ if (!gRunTestsInOneThread) {
+ int threads = -1;
+ size_t size = sizeof(threads);
+ sysctlbyname("hw.logicalcpu_max", &threads, &size, NULL, 0);
+ if (threads > 0) {
+ maxThreads = threads;
+ } else {
+ maxThreads = 8;
+ }
+ }
+ SkFILEStream inFile("../../experimental/Intersection/op.htm");
+ if (inFile.isValid()) {
+ SkTDArray<char> inData;
+ inData.setCount(inFile.getLength());
+ size_t inLen = inData.count();
+ inFile.read(inData.begin(), inLen);
+ inFile.setPath(NULL);
+ char* insert = strstr(inData.begin(), marker);
+ if (insert) {
+ insert += sizeof(marker) - 1;
+ const char* numLoc = insert + 4 /* indent spaces */ + testNameSize - 1;
+ testNumber = atoi(numLoc) + 1;
+ }
+ }
+ const char* filename = preferredFilename;
+ SkFILEWStream preferredTest(filename);
+ if (!preferredTest.isValid()) {
+ filename = backupFilename;
+ SkFILEWStream backupTest(filename);
+ SkASSERT(backupTest.isValid());
+ }
+ for (int index = 0; index < maxThreads; ++index) {
+ State4* statePtr = &threadState[index];
+ strcpy(statePtr->filename, filename);
+ size_t len = strlen(filename);
+ SkASSERT(statePtr->filename[len - 6] == 'X');
+ SkASSERT(statePtr->filename[len - 5] == 'X');
+ statePtr->filename[len - 6] = '0' + index / 10;
+ statePtr->filename[len - 5] = '0' + index % 10;
+ }
+ threadIndex = 0;
+}
+
+void outputProgress(const State4& state, const char* pathStr, SkPath::FillType pathFillType) {
+ if (gRunTestsInOneThread && gShowOutputProgress) {
+ if (pathFillType == SkPath::kEvenOdd_FillType) {
+ SkDebugf(" path.setFillType(SkPath::kEvenOdd_FillType);\n", pathStr);
+ }
+ SkDebugf("%s\n", pathStr);
+ }
+ const char testFunction[] = "testSimplifyx(path);";
+ const char* pathPrefix = NULL;
+ const char* nameSuffix = NULL;
+ if (pathFillType == SkPath::kEvenOdd_FillType) {
+ pathPrefix = " path.setFillType(SkPath::kEvenOdd_FillType);\n";
+ nameSuffix = "x";
+ }
+ if (gUsePhysicalFiles) {
+ SkFILEWStream outFile(state.filename);
+ if (!outFile.isValid()) {
+ SkASSERT(0);
+ return;
+ }
+ outputToStream(state, pathStr, pathPrefix, nameSuffix, testFunction, outFile);
+ return;
+ }
+ SkFILEWStream outRam(state.filename);
+ outputToStream(state, pathStr, pathPrefix, nameSuffix, testFunction, outRam);
+}
+
+void outputProgress(const State4& state, const char* pathStr, ShapeOp op) {
+ SkString testFunc("testShapeOp(path, pathB, ");
+ testFunc += opStrs[op];
+ testFunc += ");";
+ const char* testFunction = testFunc.c_str();
+ if (gRunTestsInOneThread && gShowOutputProgress) {
+ SkDebugf("%s\n", pathStr);
+ SkDebugf(" %s\n", testFunction);
+ }
+ const char* nameSuffix = opSuffixes[op];
+ if (gUsePhysicalFiles) {
+ SkFILEWStream outFile(state.filename);
+ if (!outFile.isValid()) {
+ SkASSERT(0);
+ return;
+ }
+ outputToStream(state, pathStr, NULL, nameSuffix, testFunction, outFile);
+ return;
+ }
+ SkFILEWStream outRam(state.filename);
+ outputToStream(state, pathStr, NULL, nameSuffix, testFunction, outRam);
+}
+
+static void writeTestName(const char* nameSuffix, SkWStream& outFile) {
+ outFile.writeText(testName);
+ outFile.writeDecAsText(testNumber);
+ if (nameSuffix) {
+ outFile.writeText(nameSuffix);
+ }
+}
+
+void outputToStream(const State4& state, const char* pathStr, const char* pathPrefix,
+ const char* nameSuffix,
+ const char* testFunction, SkWStream& outFile) {
+ outFile.writeText("<div id=\"");
+ writeTestName(nameSuffix, outFile);
+ outFile.writeText("\">\n");
+ if (pathPrefix) {
+ outFile.writeText(pathPrefix);
+ }
+ outFile.writeText(pathStr);
+ outFile.writeText("</div>\n\n");
+
+ outFile.writeText(marker);
+ outFile.writeText(" ");
+ writeTestName(nameSuffix, outFile);
+ outFile.writeText(",\n\n\n");
+
+ outFile.writeText("static void ");
+ writeTestName(nameSuffix, outFile);
+ outFile.writeText("() {\n SkPath path, pathB;\n");
+ if (pathPrefix) {
+ outFile.writeText(pathPrefix);
+ }
+ outFile.writeText(pathStr);
+ outFile.writeText(" ");
+ outFile.writeText(testFunction);
+ outFile.writeText("\n}\n\n");
+ outFile.writeText("static void (*firstTest)() = ");
+ writeTestName(nameSuffix, outFile);
+ outFile.writeText(";\n\n");
+
+ outFile.writeText("static struct {\n");
+ outFile.writeText(" void (*fun)();\n");
+ outFile.writeText(" const char* str;\n");
+ outFile.writeText("} tests[] = {\n");
+ outFile.writeText(" TEST(");
+ writeTestName(pathPrefix, outFile);
+ outFile.writeText("),\n");
+ outFile.flush();
+}
+
+bool runNextTestSet(State4& state) {
+ if (gRunTestsInOneThread) {
+ return false;
+ }
+ pthread_mutex_lock(&State4::addQueue);
+ state.done = true;
+ State4::queue = &state;
+ if (debugThreads) SkDebugf("%s %d checkQueue done=%d last=%d\n", __FUNCTION__, state.index,
+ state.done, state.last);
+ pthread_cond_signal(&State4::checkQueue);
+ while (state.done && !state.last) {
+ if (debugThreads) SkDebugf("%s %d done=%d last=%d\n", __FUNCTION__, state.index, state.done, state.last);
+ pthread_cond_wait(&state.initialized, &State4::addQueue);
+ }
+ pthread_mutex_unlock(&State4::addQueue);
+ return !state.last;
+}
+
+int waitForCompletion() {
+ int testsRun = 0;
+ if (!gRunTestsInOneThread) {
+ pthread_mutex_lock(&State4::addQueue);
+ int runningThreads = maxThreads;
+ int index;
+ while (runningThreads > 0) {
+ while (!State4::queue) {
+ if (debugThreads) SkDebugf("%s checkQueue\n", __FUNCTION__);
+ pthread_cond_wait(&State4::checkQueue, &State4::addQueue);
+ }
+ while (State4::queue) {
+ --runningThreads;
+ SkDebugf("•");
+ State4::queue->last = true;
+ State4* next = NULL;
+ for (index = 0; index < maxThreads; ++index) {
+ State4& test = threadState[index];
+ if (test.done && !test.last) {
+ next = &test;
+ }
+ }
+ if (debugThreads) SkDebugf("%s %d next=%d deQueue\n", __FUNCTION__,
+ State4::queue->index, next ? next->index : -1);
+ pthread_cond_signal(&State4::queue->initialized);
+ State4::queue = next;
+ }
+ }
+ pthread_mutex_unlock(&State4::addQueue);
+ for (index = 0; index < maxThreads; ++index) {
+ pthread_join(threadState[index].threadID, NULL);
+ testsRun += threadState[index].testsRun;
+ }
+ SkDebugf("\n");
+ }
+#ifdef SK_DEBUG
+ gDebugMaxWindSum = SK_MaxS32;
+ gDebugMaxWindValue = SK_MaxS32;
+#endif
+ return testsRun;
+}
diff --git a/experimental/Intersection/Intersection_Tests.cpp b/experimental/Intersection/Intersection_Tests.cpp
new file mode 100644
index 0000000000..9ae4ff59d0
--- /dev/null
+++ b/experimental/Intersection/Intersection_Tests.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "CubicIntersection_TestData.h"
+#include "Intersection_Tests.h"
+#include "SkTypes.h"
+
+void cubecode_test(int test);
+
+#define TEST_QUADS_FIRST 0
+
+void Intersection_Tests() {
+ int testsRun = 0;
+
+ SimplifyNew_Test();
+ Simplify4x4QuadraticsThreaded_Test(testsRun);
+ QuadLineIntersectThreaded_Test(testsRun);
+ Simplify4x4RectsThreaded_Test(testsRun);
+ SimplifyNondegenerate4x4TrianglesThreaded_Test(testsRun);
+ SimplifyDegenerate4x4TrianglesThreaded_Test(testsRun);
+ Simplify4x4QuadralateralsThreaded_Test(testsRun);
+ ShapeOps4x4RectsThreaded_Test(testsRun);
+ SkDebugf("%s total testsRun=%d\n", __FUNCTION__, testsRun);
+ QuadraticIntersection_Test();
+ LineQuadraticIntersection_Test();
+ MiniSimplify_Test();
+ SimplifyAngle_Test();
+ QuarticRoot_Test();
+ QuadraticBezierClip_Test();
+ SimplifyFindNext_Test();
+ SimplifyFindTop_Test();
+ QuadraticReduceOrder_Test();
+ SimplifyAddIntersectingTs_Test();
+
+ cubecode_test(1);
+ convert_testx();
+ // tests are in dependency / complexity order
+ Inline_Tests();
+ ConvexHull_Test();
+ ConvexHull_X_Test();
+
+ LineParameter_Test();
+ LineIntersection_Test();
+ LineCubicIntersection_Test();
+
+ SimplifyQuadraticPaths_Test();
+
+ SimplifyPolygonPaths_Test();
+ SimplifyRectangularPaths_Test();
+ SimplifyQuadralateralPaths_Test();
+
+ ActiveEdge_Test();
+
+ QuadraticCoincidence_Test();
+ QuadraticIntersection_Test();
+
+ CubicParameterization_Test();
+ CubicCoincidence_Test();
+ CubicReduceOrder_Test();
+ CubicBezierClip_Test();
+ CubicIntersection_Test();
+
+}
diff --git a/experimental/Intersection/Intersection_Tests.h b/experimental/Intersection/Intersection_Tests.h
new file mode 100644
index 0000000000..46e6b7b355
--- /dev/null
+++ b/experimental/Intersection/Intersection_Tests.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#if !defined(IN_TEST)
+ #define IN_TEST 1
+#endif
+
+void ActiveEdge_Test();
+void ConvexHull_Test();
+void ConvexHull_X_Test();
+void CubicBezierClip_Test();
+void CubicCoincidence_Test();
+void CubicIntersection_Test();
+void CubicParameterization_Test();
+void CubicReduceOrder_Test();
+void Inline_Tests();
+void Intersection_Tests();
+void LineCubicIntersection_Test();
+void LineIntersection_Test();
+void LineParameter_Test();
+void LineQuadraticIntersection_Test();
+void MiniSimplify_Test();
+void SimplifyAddIntersectingTs_Test();
+void SimplifyAngle_Test();
+void SimplifyDegenerate4x4TrianglesThreaded_Test(int& );
+void SimplifyFindNext_Test();
+void SimplifyFindTop_Test();
+void SimplifyNew_Test();
+void SimplifyNondegenerate4x4TrianglesThreaded_Test(int& );
+void SimplifyPolygonPaths_Test();
+void SimplifyQuadralateralPaths_Test();
+void SimplifyQuadraticPaths_Test();
+void Simplify4x4QuadralateralsThreaded_Test(int& );
+void Simplify4x4QuadraticsThreaded_Test(int& );
+void Simplify4x4RectsThreaded_Test(int& );
+void SimplifyRectangularPaths_Test();
+void ShapeOps4x4RectsThreaded_Test(int& );
+void QuadLineIntersectThreaded_Test(int& );
+void QuadraticBezierClip_Test();
+void QuadraticCoincidence_Test();
+void QuadraticIntersection_Test();
+void QuadraticParameterization_Test();
+void QuadraticReduceOrder_Test();
+void QuarticRoot_Test();
diff --git a/experimental/Intersection/Intersections.h b/experimental/Intersection/Intersections.h
new file mode 100644
index 0000000000..fe12b25902
--- /dev/null
+++ b/experimental/Intersection/Intersections.h
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef Intersections_DEFINE
+#define Intersections_DEFINE
+
+#include <algorithm> // for std::min
+
+class Intersections {
+public:
+ Intersections()
+ : fUsed(0)
+ , fUsed2(0)
+ , fCoincidentUsed(0)
+ , fSwap(0)
+ , fFlip(0)
+ {
+ // OPTIMIZE: don't need to be initialized in release
+ bzero(fT, sizeof(fT));
+ bzero(fCoincidentT, sizeof(fCoincidentT));
+ }
+
+ void add(double one, double two) {
+ for (int index = 0; index < fUsed; ++index) {
+ if (approximately_equal(fT[fSwap][index], one)
+ && approximately_equal(fT[fSwap ^ 1][index], two)) {
+ return;
+ }
+ }
+ assert(fUsed < 9);
+ fT[fSwap][fUsed] = one;
+ fT[fSwap ^ 1][fUsed] = two;
+ ++fUsed;
+ }
+
+ // start if index == 0 : end if index == 1
+ void addCoincident(double one, double two) {
+ for (int index = 0; index < fCoincidentUsed; ++index) {
+ if (approximately_equal(fCoincidentT[fSwap][index], one)
+ && approximately_equal(fCoincidentT[fSwap ^ 1][index], two)) {
+ return;
+ }
+ }
+ assert(fCoincidentUsed < 9);
+ fCoincidentT[fSwap][fCoincidentUsed] = one;
+ fCoincidentT[fSwap ^ 1][fCoincidentUsed] = two;
+ ++fCoincidentUsed;
+ }
+
+ void addCoincident(double s1, double e1, double s2, double e2) {
+ assert((fCoincidentUsed & 1) != 1);
+ for (int index = 0; index < fCoincidentUsed; index += 2) {
+ double cs1 = fCoincidentT[fSwap][index];
+ double ce1 = fCoincidentT[fSwap][index + 1];
+ bool s1in = approximately_between(cs1, s1, ce1);
+ bool e1in = approximately_between(cs1, e1, ce1);
+ double cs2 = fCoincidentT[fSwap ^ 1][index];
+ double ce2 = fCoincidentT[fSwap ^ 1][index + 1];
+ bool s2in = approximately_between(cs2, s2, ce2);
+ bool e2in = approximately_between(cs2, e2, ce2);
+ if ((s1in | e1in) & (s2in | e2in)) {
+ double lesser1 = std::min(cs1, ce1);
+ index += cs1 > ce1;
+ if (s1in < lesser1) {
+ fCoincidentT[fSwap][index] = s1in;
+ } else if (e1in < lesser1) {
+ fCoincidentT[fSwap][index] = e1in;
+ }
+ index ^= 1;
+ double greater1 = fCoincidentT[fSwap][index];
+ if (s1in > greater1) {
+ fCoincidentT[fSwap][index] = s1in;
+ } else if (e1in > greater1) {
+ fCoincidentT[fSwap][index] = e1in;
+ }
+ index &= ~1;
+ double lesser2 = std::min(cs2, ce2);
+ index += cs2 > ce2;
+ if (s2in < lesser2) {
+ fCoincidentT[fSwap ^ 1][index] = s2in;
+ } else if (e2in < lesser2) {
+ fCoincidentT[fSwap ^ 1][index] = e2in;
+ }
+ index ^= 1;
+ double greater2 = fCoincidentT[fSwap ^ 1][index];
+ if (s2in > greater2) {
+ fCoincidentT[fSwap ^ 1][index] = s2in;
+ } else if (e2in > greater2) {
+ fCoincidentT[fSwap ^ 1][index] = e2in;
+ }
+ return;
+ }
+ }
+ assert(fCoincidentUsed < 9);
+ fCoincidentT[fSwap][fCoincidentUsed] = s1;
+ fCoincidentT[fSwap ^ 1][fCoincidentUsed] = s2;
+ ++fCoincidentUsed;
+ fCoincidentT[fSwap][fCoincidentUsed] = e1;
+ fCoincidentT[fSwap ^ 1][fCoincidentUsed] = e2;
+ ++fCoincidentUsed;
+ }
+
+ // FIXME: this is necessary because curve/curve intersections are noisy
+ // remove once curve/curve intersections are improved
+ void cleanUp();
+
+ int coincidentUsed() {
+ return fCoincidentUsed;
+ }
+
+ void offset(int base, double start, double end) {
+ for (int index = base; index < fUsed; ++index) {
+ double val = fT[fSwap][index];
+ val *= end - start;
+ val += start;
+ fT[fSwap][index] = val;
+ }
+ }
+
+ void insert(double one, double two) {
+ assert(fUsed <= 1 || fT[0][0] < fT[0][1]);
+ int index;
+ for (index = 0; index < fUsed; ++index) {
+ if (approximately_equal(fT[0][index], one)
+ && approximately_equal(fT[1][index], two)) {
+ return;
+ }
+ if (fT[0][index] > one) {
+ break;
+ }
+ }
+ assert(fUsed < 9);
+ int remaining = fUsed - index;
+ if (remaining > 0) {
+ memmove(&fT[0][index + 1], &fT[0][index], sizeof(fT[0][0]) * remaining);
+ memmove(&fT[1][index + 1], &fT[1][index], sizeof(fT[1][0]) * remaining);
+ }
+ fT[0][index] = one;
+ fT[1][index] = two;
+ ++fUsed;
+ }
+
+ // FIXME: all callers should be moved to regular insert. Failures are likely
+ // if two separate callers differ on whether ts are equal or not
+ void insertOne(double t, int side) {
+ int used = side ? fUsed2 : fUsed;
+ assert(used <= 1 || fT[side][0] < fT[side][1]);
+ int index;
+ for (index = 0; index < used; ++index) {
+ if (approximately_equal(fT[side][index], t)) {
+ return;
+ }
+ if (fT[side][index] > t) {
+ break;
+ }
+ }
+ assert(used < 9);
+ int remaining = used - index;
+ if (remaining > 0) {
+ memmove(&fT[side][index + 1], &fT[side][index], sizeof(fT[side][0]) * remaining);
+ }
+ fT[side][index] = t;
+ side ? ++fUsed2 : ++fUsed;
+ }
+
+ bool intersected() const {
+ return fUsed > 0;
+ }
+
+ bool insertBalanced() const {
+ return fUsed == fUsed2;
+ }
+
+ void swap() {
+ fSwap ^= 1;
+ }
+
+ bool swapped() {
+ return fSwap;
+ }
+
+ int used() {
+ return fUsed;
+ }
+
+ double fT[2][9];
+ double fCoincidentT[2][9];
+ int fUsed;
+ int fUsed2;
+ int fCoincidentUsed;
+ int fFlip;
+private:
+ int fSwap;
+};
+
+#endif
+
diff --git a/experimental/Intersection/LineIntersection.cpp b/experimental/Intersection/LineIntersection.cpp
new file mode 100644
index 0000000000..c425cd1ff5
--- /dev/null
+++ b/experimental/Intersection/LineIntersection.cpp
@@ -0,0 +1,325 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "CurveIntersection.h"
+#include "Intersections.h"
+#include "LineIntersection.h"
+#include <algorithm> // used for std::swap
+
+
+/*
+ Determine the intersection point of two line segments
+ Return FALSE if the lines don't intersect
+ from: http://paulbourke.net/geometry/lineline2d/
+*/
+
+int intersect(const _Line& a, const _Line& b, double aRange[2], double bRange[2]) {
+ double axLen = a[1].x - a[0].x;
+ double ayLen = a[1].y - a[0].y;
+ double bxLen = b[1].x - b[0].x;
+ double byLen = b[1].y - b[0].y;
+ /* Slopes match when denom goes to zero:
+ axLen / ayLen == bxLen / byLen
+ (ayLen * byLen) * axLen / ayLen == (ayLen * byLen) * bxLen / byLen
+ byLen * axLen == ayLen * bxLen
+ byLen * axLen - ayLen * bxLen == 0 ( == denom )
+ */
+ double denom = byLen * axLen - ayLen * bxLen;
+ if (approximately_zero(denom)) {
+ /* See if the axis intercepts match:
+ ay - ax * ayLen / axLen == by - bx * ayLen / axLen
+ axLen * (ay - ax * ayLen / axLen) == axLen * (by - bx * ayLen / axLen)
+ axLen * ay - ax * ayLen == axLen * by - bx * ayLen
+ */
+ if (approximately_equal_squared(axLen * a[0].y - ayLen * a[0].x,
+ axLen * b[0].y - ayLen * b[0].x)) {
+ const double* aPtr;
+ const double* bPtr;
+ if (fabs(axLen) > fabs(ayLen) || fabs(bxLen) > fabs(byLen)) {
+ aPtr = &a[0].x;
+ bPtr = &b[0].x;
+ } else {
+ aPtr = &a[0].y;
+ bPtr = &b[0].y;
+ }
+ #if 0 // sorting edges fails to preserve original direction
+ double aMin = aPtr[0];
+ double aMax = aPtr[2];
+ double bMin = bPtr[0];
+ double bMax = bPtr[2];
+ if (aMin > aMax) {
+ std::swap(aMin, aMax);
+ }
+ if (bMin > bMax) {
+ std::swap(bMin, bMax);
+ }
+ if (aMax < bMin || bMax < aMin) {
+ return 0;
+ }
+ if (aRange) {
+ aRange[0] = bMin <= aMin ? 0 : (bMin - aMin) / (aMax - aMin);
+ aRange[1] = bMax >= aMax ? 1 : (bMax - aMin) / (aMax - aMin);
+ }
+ int bIn = (aPtr[0] - aPtr[2]) * (bPtr[0] - bPtr[2]) < 0;
+ if (bRange) {
+ bRange[bIn] = aMin <= bMin ? 0 : (aMin - bMin) / (bMax - bMin);
+ bRange[!bIn] = aMax >= bMax ? 1 : (aMax - bMin) / (bMax - bMin);
+ }
+ return 1 + ((aRange[0] != aRange[1]) || (bRange[0] != bRange[1]));
+ #else
+ assert(aRange);
+ assert(bRange);
+ double a0 = aPtr[0];
+ double a1 = aPtr[2];
+ double b0 = bPtr[0];
+ double b1 = bPtr[2];
+ double at0 = (a0 - b0) / (a0 - a1);
+ double at1 = (a0 - b1) / (a0 - a1);
+ if ((at0 < 0 && at1 < 0) || (at0 > 1 && at1 > 1)) {
+ return 0;
+ }
+ aRange[0] = std::max(std::min(at0, 1.0), 0.0);
+ aRange[1] = std::max(std::min(at1, 1.0), 0.0);
+ int bIn = (a0 - a1) * (b0 - b1) < 0;
+ bRange[bIn] = std::max(std::min((b0 - a0) / (b0 - b1), 1.0), 0.0);
+ bRange[!bIn] = std::max(std::min((b0 - a1) / (b0 - b1), 1.0), 0.0);
+ bool second = fabs(aRange[0] - aRange[1]) > FLT_EPSILON;
+ assert((fabs(bRange[0] - bRange[1]) <= FLT_EPSILON) ^ second);
+ return 1 + second;
+ #endif
+ }
+ }
+ double ab0y = a[0].y - b[0].y;
+ double ab0x = a[0].x - b[0].x;
+ double numerA = ab0y * bxLen - byLen * ab0x;
+ if ((numerA < 0 && denom > numerA) || (numerA > 0 && denom < numerA)) {
+ return 0;
+ }
+ double numerB = ab0y * axLen - ayLen * ab0x;
+ if ((numerB < 0 && denom > numerB) || (numerB > 0 && denom < numerB)) {
+ return 0;
+ }
+ /* Is the intersection along the the segments */
+ if (aRange) {
+ aRange[0] = numerA / denom;
+ }
+ if (bRange) {
+ bRange[0] = numerB / denom;
+ }
+ return 1;
+}
+
+int horizontalIntersect(const _Line& line, double y, double tRange[2]) {
+ double min = line[0].y;
+ double max = line[1].y;
+ if (min > max) {
+ std::swap(min, max);
+ }
+ if (min > y || max < y) {
+ return 0;
+ }
+ if (approximately_equal(min, max)) {
+ tRange[0] = 0;
+ tRange[1] = 1;
+ return 2;
+ }
+ tRange[0] = (y - line[0].y) / (line[1].y - line[0].y);
+ return 1;
+}
+
+// OPTIMIZATION Given: dy = line[1].y - line[0].y
+// and: xIntercept / (y - line[0].y) == (line[1].x - line[0].x) / dy
+// then: xIntercept * dy == (line[1].x - line[0].x) * (y - line[0].y)
+// Assuming that dy is always > 0, the line segment intercepts if:
+// left * dy <= xIntercept * dy <= right * dy
+// thus: left * dy <= (line[1].x - line[0].x) * (y - line[0].y) <= right * dy
+// (clever as this is, it does not give us the t value, so may be useful only
+// as a quick reject -- and maybe not then; it takes 3 muls, 3 adds, 2 cmps)
+int horizontalLineIntersect(const _Line& line, double left, double right,
+ double y, double tRange[2]) {
+ int result = horizontalIntersect(line, y, tRange);
+ if (result != 1) {
+ // FIXME: this is incorrect if result == 2
+ return result;
+ }
+ double xIntercept = line[0].x + tRange[0] * (line[1].x - line[0].x);
+ if (xIntercept > right || xIntercept < left) {
+ return 0;
+ }
+ return result;
+}
+
+int horizontalIntersect(const _Line& line, double left, double right,
+ double y, bool flipped, Intersections& intersections) {
+ int result = horizontalIntersect(line, y, intersections.fT[0]);
+ switch (result) {
+ case 0:
+ break;
+ case 1: {
+ double xIntercept = line[0].x + intersections.fT[0][0]
+ * (line[1].x - line[0].x);
+ if (xIntercept > right || xIntercept < left) {
+ return 0;
+ }
+ intersections.fT[1][0] = (xIntercept - left) / (right - left);
+ break;
+ }
+ case 2:
+ #if 0 // sorting edges fails to preserve original direction
+ double lineL = line[0].x;
+ double lineR = line[1].x;
+ if (lineL > lineR) {
+ std::swap(lineL, lineR);
+ }
+ double overlapL = std::max(left, lineL);
+ double overlapR = std::min(right, lineR);
+ if (overlapL > overlapR) {
+ return 0;
+ }
+ if (overlapL == overlapR) {
+ result = 1;
+ }
+ intersections.fT[0][0] = (overlapL - line[0].x) / (line[1].x - line[0].x);
+ intersections.fT[1][0] = (overlapL - left) / (right - left);
+ if (result > 1) {
+ intersections.fT[0][1] = (overlapR - line[0].x) / (line[1].x - line[0].x);
+ intersections.fT[1][1] = (overlapR - left) / (right - left);
+ }
+ #else
+ double a0 = line[0].x;
+ double a1 = line[1].x;
+ double b0 = flipped ? right : left;
+ double b1 = flipped ? left : right;
+ // FIXME: share common code below
+ double at0 = (a0 - b0) / (a0 - a1);
+ double at1 = (a0 - b1) / (a0 - a1);
+ if ((at0 < 0 && at1 < 0) || (at0 > 1 && at1 > 1)) {
+ return 0;
+ }
+ intersections.fT[0][0] = std::max(std::min(at0, 1.0), 0.0);
+ intersections.fT[0][1] = std::max(std::min(at1, 1.0), 0.0);
+ int bIn = (a0 - a1) * (b0 - b1) < 0;
+ intersections.fT[1][bIn] = std::max(std::min((b0 - a0) / (b0 - b1),
+ 1.0), 0.0);
+ intersections.fT[1][!bIn] = std::max(std::min((b0 - a1) / (b0 - b1),
+ 1.0), 0.0);
+ bool second = fabs(intersections.fT[0][0] - intersections.fT[0][1])
+ > FLT_EPSILON;
+ assert((fabs(intersections.fT[1][0] - intersections.fT[1][1])
+ <= FLT_EPSILON) ^ second);
+ return 1 + second;
+ #endif
+ break;
+ }
+ if (flipped) {
+ // OPTIMIZATION: instead of swapping, pass original line, use [1].x - [0].x
+ for (int index = 0; index < result; ++index) {
+ intersections.fT[1][index] = 1 - intersections.fT[1][index];
+ }
+ }
+ return result;
+}
+
+static int verticalIntersect(const _Line& line, double x, double tRange[2]) {
+ double min = line[0].x;
+ double max = line[1].x;
+ if (min > max) {
+ std::swap(min, max);
+ }
+ if (min > x || max < x) {
+ return 0;
+ }
+ if (approximately_equal(min, max)) {
+ tRange[0] = 0;
+ tRange[1] = 1;
+ return 2;
+ }
+ tRange[0] = (x - line[0].x) / (line[1].x - line[0].x);
+ return 1;
+}
+
+int verticalIntersect(const _Line& line, double top, double bottom,
+ double x, bool flipped, Intersections& intersections) {
+ int result = verticalIntersect(line, x, intersections.fT[0]);
+ switch (result) {
+ case 0:
+ break;
+ case 1: {
+ double yIntercept = line[0].y + intersections.fT[0][0]
+ * (line[1].y - line[0].y);
+ if (yIntercept > bottom || yIntercept < top) {
+ return 0;
+ }
+ intersections.fT[1][0] = (yIntercept - top) / (bottom - top);
+ break;
+ }
+ case 2:
+ #if 0 // sorting edges fails to preserve original direction
+ double lineT = line[0].y;
+ double lineB = line[1].y;
+ if (lineT > lineB) {
+ std::swap(lineT, lineB);
+ }
+ double overlapT = std::max(top, lineT);
+ double overlapB = std::min(bottom, lineB);
+ if (overlapT > overlapB) {
+ return 0;
+ }
+ if (overlapT == overlapB) {
+ result = 1;
+ }
+ intersections.fT[0][0] = (overlapT - line[0].y) / (line[1].y - line[0].y);
+ intersections.fT[1][0] = (overlapT - top) / (bottom - top);
+ if (result > 1) {
+ intersections.fT[0][1] = (overlapB - line[0].y) / (line[1].y - line[0].y);
+ intersections.fT[1][1] = (overlapB - top) / (bottom - top);
+ }
+ #else
+ double a0 = line[0].y;
+ double a1 = line[1].y;
+ double b0 = flipped ? bottom : top;
+ double b1 = flipped ? top : bottom;
+ // FIXME: share common code above
+ double at0 = (a0 - b0) / (a0 - a1);
+ double at1 = (a0 - b1) / (a0 - a1);
+ if ((at0 < 0 && at1 < 0) || (at0 > 1 && at1 > 1)) {
+ return 0;
+ }
+ intersections.fT[0][0] = std::max(std::min(at0, 1.0), 0.0);
+ intersections.fT[0][1] = std::max(std::min(at1, 1.0), 0.0);
+ int bIn = (a0 - a1) * (b0 - b1) < 0;
+ intersections.fT[1][bIn] = std::max(std::min((b0 - a0) / (b0 - b1),
+ 1.0), 0.0);
+ intersections.fT[1][!bIn] = std::max(std::min((b0 - a1) / (b0 - b1),
+ 1.0), 0.0);
+ bool second = fabs(intersections.fT[0][0] - intersections.fT[0][1])
+ > FLT_EPSILON;
+ assert((fabs(intersections.fT[1][0] - intersections.fT[1][1])
+ <= FLT_EPSILON) ^ second);
+ return 1 + second;
+ #endif
+ break;
+ }
+ if (flipped) {
+ // OPTIMIZATION: instead of swapping, pass original line, use [1].y - [0].y
+ for (int index = 0; index < result; ++index) {
+ intersections.fT[1][index] = 1 - intersections.fT[1][index];
+ }
+ }
+ return result;
+}
+
+// from http://www.bryceboe.com/wordpress/wp-content/uploads/2006/10/intersect.py
+// 4 subs, 2 muls, 1 cmp
+static bool ccw(const _Point& A, const _Point& B, const _Point& C) {
+ return (C.y - A.y) * (B.x - A.x) > (B.y - A.y) * (C.x - A.x);
+}
+
+// 16 subs, 8 muls, 6 cmps
+bool testIntersect(const _Line& a, const _Line& b) {
+ return ccw(a[0], b[0], b[1]) != ccw(a[1], b[0], b[1])
+ && ccw(a[0], a[1], b[0]) != ccw(a[0], a[1], b[1]);
+}
diff --git a/experimental/Intersection/LineQuadraticIntersection.cpp b/experimental/Intersection/LineQuadraticIntersection.cpp
new file mode 100644
index 0000000000..b3303cf95e
--- /dev/null
+++ b/experimental/Intersection/LineQuadraticIntersection.cpp
@@ -0,0 +1,367 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "CurveIntersection.h"
+#include "Intersections.h"
+#include "LineUtilities.h"
+#include "QuadraticUtilities.h"
+
+/*
+Find the interection of a line and quadratic by solving for valid t values.
+
+From http://stackoverflow.com/questions/1853637/how-to-find-the-mathematical-function-defining-a-bezier-curve
+
+"A Bezier curve is a parametric function. A quadratic Bezier curve (i.e. three
+control points) can be expressed as: F(t) = A(1 - t)^2 + B(1 - t)t + Ct^2 where
+A, B and C are points and t goes from zero to one.
+
+This will give you two equations:
+
+ x = a(1 - t)^2 + b(1 - t)t + ct^2
+ y = d(1 - t)^2 + e(1 - t)t + ft^2
+
+If you add for instance the line equation (y = kx + m) to that, you'll end up
+with three equations and three unknowns (x, y and t)."
+
+Similar to above, the quadratic is represented as
+ x = a(1-t)^2 + 2b(1-t)t + ct^2
+ y = d(1-t)^2 + 2e(1-t)t + ft^2
+and the line as
+ y = g*x + h
+
+Using Mathematica, solve for the values of t where the quadratic intersects the
+line:
+
+ (in) t1 = Resultant[a*(1 - t)^2 + 2*b*(1 - t)*t + c*t^2 - x,
+ d*(1 - t)^2 + 2*e*(1 - t)*t + f*t^2 - g*x - h, x]
+ (out) -d + h + 2 d t - 2 e t - d t^2 + 2 e t^2 - f t^2 +
+ g (a - 2 a t + 2 b t + a t^2 - 2 b t^2 + c t^2)
+ (in) Solve[t1 == 0, t]
+ (out) {
+ {t -> (-2 d + 2 e + 2 a g - 2 b g -
+ Sqrt[(2 d - 2 e - 2 a g + 2 b g)^2 -
+ 4 (-d + 2 e - f + a g - 2 b g + c g) (-d + a g + h)]) /
+ (2 (-d + 2 e - f + a g - 2 b g + c g))
+ },
+ {t -> (-2 d + 2 e + 2 a g - 2 b g +
+ Sqrt[(2 d - 2 e - 2 a g + 2 b g)^2 -
+ 4 (-d + 2 e - f + a g - 2 b g + c g) (-d + a g + h)]) /
+ (2 (-d + 2 e - f + a g - 2 b g + c g))
+ }
+ }
+
+Using the results above (when the line tends towards horizontal)
+ A = (-(d - 2*e + f) + g*(a - 2*b + c) )
+ B = 2*( (d - e ) - g*(a - b ) )
+ C = (-(d ) + g*(a ) + h )
+
+If g goes to infinity, we can rewrite the line in terms of x.
+ x = g'*y + h'
+
+And solve accordingly in Mathematica:
+
+ (in) t2 = Resultant[a*(1 - t)^2 + 2*b*(1 - t)*t + c*t^2 - g'*y - h',
+ d*(1 - t)^2 + 2*e*(1 - t)*t + f*t^2 - y, y]
+ (out) a - h' - 2 a t + 2 b t + a t^2 - 2 b t^2 + c t^2 -
+ g' (d - 2 d t + 2 e t + d t^2 - 2 e t^2 + f t^2)
+ (in) Solve[t2 == 0, t]
+ (out) {
+ {t -> (2 a - 2 b - 2 d g' + 2 e g' -
+ Sqrt[(-2 a + 2 b + 2 d g' - 2 e g')^2 -
+ 4 (a - 2 b + c - d g' + 2 e g' - f g') (a - d g' - h')]) /
+ (2 (a - 2 b + c - d g' + 2 e g' - f g'))
+ },
+ {t -> (2 a - 2 b - 2 d g' + 2 e g' +
+ Sqrt[(-2 a + 2 b + 2 d g' - 2 e g')^2 -
+ 4 (a - 2 b + c - d g' + 2 e g' - f g') (a - d g' - h')])/
+ (2 (a - 2 b + c - d g' + 2 e g' - f g'))
+ }
+ }
+
+Thus, if the slope of the line tends towards vertical, we use:
+ A = ( (a - 2*b + c) - g'*(d - 2*e + f) )
+ B = 2*(-(a - b ) + g'*(d - e ) )
+ C = ( (a ) - g'*(d ) - h' )
+ */
+
+
+class LineQuadraticIntersections {
+public:
+
+LineQuadraticIntersections(const Quadratic& q, const _Line& l, Intersections& i)
+ : quad(q)
+ , line(l)
+ , intersections(i) {
+}
+
+int intersectRay(double roots[2]) {
+/*
+ solve by rotating line+quad so line is horizontal, then finding the roots
+ set up matrix to rotate quad to x-axis
+ |cos(a) -sin(a)|
+ |sin(a) cos(a)|
+ note that cos(a) = A(djacent) / Hypoteneuse
+ sin(a) = O(pposite) / Hypoteneuse
+ since we are computing Ts, we can ignore hypoteneuse, the scale factor:
+ | A -O |
+ | O A |
+ A = line[1].x - line[0].x (adjacent side of the right triangle)
+ O = line[1].y - line[0].y (opposite side of the right triangle)
+ for each of the three points (e.g. n = 0 to 2)
+ quad[n].y' = (quad[n].y - line[0].y) * A - (quad[n].x - line[0].x) * O
+*/
+ double adj = line[1].x - line[0].x;
+ double opp = line[1].y - line[0].y;
+ double r[3];
+ for (int n = 0; n < 3; ++n) {
+ r[n] = (quad[n].y - line[0].y) * adj - (quad[n].x - line[0].x) * opp;
+ }
+ double A = r[2];
+ double B = r[1];
+ double C = r[0];
+ A += C - 2 * B; // A = a - 2*b + c
+ B -= C; // B = -(b - c)
+ return quadraticRoots(A, B, C, roots);
+}
+
+int intersect() {
+ addEndPoints();
+ double rootVals[2];
+ int roots = intersectRay(rootVals);
+ for (int index = 0; index < roots; ++index) {
+ double quadT = rootVals[index];
+ double lineT = findLineT(quadT);
+ if (pinTs(quadT, lineT)) {
+ intersections.insert(quadT, lineT);
+ }
+ }
+ return intersections.fUsed;
+}
+
+int horizontalIntersect(double axisIntercept, double roots[2]) {
+ double D = quad[2].y; // f
+ double E = quad[1].y; // e
+ double F = quad[0].y; // d
+ D += F - 2 * E; // D = d - 2*e + f
+ E -= F; // E = -(d - e)
+ F -= axisIntercept;
+ return quadraticRoots(D, E, F, roots);
+}
+
+int horizontalIntersect(double axisIntercept, double left, double right, bool flipped) {
+ addHorizontalEndPoints(left, right, axisIntercept);
+ double rootVals[2];
+ int roots = horizontalIntersect(axisIntercept, rootVals);
+ for (int index = 0; index < roots; ++index) {
+ double x;
+ double quadT = rootVals[index];
+ xy_at_t(quad, quadT, x, *(double*) NULL);
+ double lineT = (x - left) / (right - left);
+ if (pinTs(quadT, lineT)) {
+ intersections.insert(quadT, lineT);
+ }
+ }
+ if (flipped) {
+ flip();
+ }
+ return intersections.fUsed;
+}
+
+int verticalIntersect(double axisIntercept, double roots[2]) {
+ double D = quad[2].x; // f
+ double E = quad[1].x; // e
+ double F = quad[0].x; // d
+ D += F - 2 * E; // D = d - 2*e + f
+ E -= F; // E = -(d - e)
+ F -= axisIntercept;
+ return quadraticRoots(D, E, F, roots);
+}
+
+int verticalIntersect(double axisIntercept, double top, double bottom, bool flipped) {
+ addVerticalEndPoints(top, bottom, axisIntercept);
+ double rootVals[2];
+ int roots = verticalIntersect(axisIntercept, rootVals);
+ for (int index = 0; index < roots; ++index) {
+ double y;
+ double quadT = rootVals[index];
+ xy_at_t(quad, quadT, *(double*) NULL, y);
+ double lineT = (y - top) / (bottom - top);
+ if (pinTs(quadT, lineT)) {
+ intersections.insert(quadT, lineT);
+ }
+ }
+ if (flipped) {
+ flip();
+ }
+ return intersections.fUsed;
+}
+
+protected:
+
+// add endpoints first to get zero and one t values exactly
+void addEndPoints()
+{
+ for (int qIndex = 0; qIndex < 3; qIndex += 2) {
+ for (int lIndex = 0; lIndex < 2; lIndex++) {
+ if (quad[qIndex] == line[lIndex]) {
+ intersections.insert(qIndex >> 1, lIndex);
+ }
+ }
+ }
+}
+
+void addHorizontalEndPoints(double left, double right, double y)
+{
+ for (int qIndex = 0; qIndex < 3; qIndex += 2) {
+ if (quad[qIndex].y != y) {
+ continue;
+ }
+ if (quad[qIndex].x == left) {
+ intersections.insert(qIndex >> 1, 0);
+ }
+ if (quad[qIndex].x == right) {
+ intersections.insert(qIndex >> 1, 1);
+ }
+ }
+}
+
+void addVerticalEndPoints(double top, double bottom, double x)
+{
+ for (int qIndex = 0; qIndex < 3; qIndex += 2) {
+ if (quad[qIndex].x != x) {
+ continue;
+ }
+ if (quad[qIndex].y == top) {
+ intersections.insert(qIndex >> 1, 0);
+ }
+ if (quad[qIndex].y == bottom) {
+ intersections.insert(qIndex >> 1, 1);
+ }
+ }
+}
+
+double findLineT(double t) {
+ double x, y;
+ xy_at_t(quad, t, x, y);
+ double dx = line[1].x - line[0].x;
+ double dy = line[1].y - line[0].y;
+ if (fabs(dx) > fabs(dy)) {
+ return (x - line[0].x) / dx;
+ }
+ return (y - line[0].y) / dy;
+}
+
+void flip() {
+ // OPTIMIZATION: instead of swapping, pass original line, use [1].y - [0].y
+ int roots = intersections.fUsed;
+ for (int index = 0; index < roots; ++index) {
+ intersections.fT[1][index] = 1 - intersections.fT[1][index];
+ }
+}
+
+bool pinTs(double& quadT, double& lineT) {
+ if (!approximately_one_or_less(lineT)) {
+ return false;
+ }
+ if (!approximately_zero_or_more(lineT)) {
+ return false;
+ }
+ if (quadT < 0) {
+ quadT = 0;
+ } else if (quadT > 1) {
+ quadT = 1;
+ }
+ if (lineT < 0) {
+ lineT = 0;
+ } else if (lineT > 1) {
+ lineT = 1;
+ }
+ return true;
+}
+
+private:
+
+const Quadratic& quad;
+const _Line& line;
+Intersections& intersections;
+};
+
+// utility for pairs of coincident quads
+static double horizontalIntersect(const Quadratic& quad, const _Point& pt) {
+ LineQuadraticIntersections q(quad, *((_Line*) 0), *((Intersections*) 0));
+ double rootVals[2];
+ int roots = q.horizontalIntersect(pt.y, rootVals);
+ for (int index = 0; index < roots; ++index) {
+ double x;
+ double t = rootVals[index];
+ xy_at_t(quad, t, x, *(double*) 0);
+ if (approximately_equal(x, pt.x)) {
+ return t;
+ }
+ }
+ return -1;
+}
+
+static double verticalIntersect(const Quadratic& quad, const _Point& pt) {
+ LineQuadraticIntersections q(quad, *((_Line*) 0), *((Intersections*) 0));
+ double rootVals[2];
+ int roots = q.verticalIntersect(pt.x, rootVals);
+ for (int index = 0; index < roots; ++index) {
+ double y;
+ double t = rootVals[index];
+ xy_at_t(quad, t, *(double*) 0, y);
+ if (approximately_equal(y, pt.y)) {
+ return t;
+ }
+ }
+ return -1;
+}
+
+double axialIntersect(const Quadratic& q1, const _Point& p, bool vertical) {
+ if (vertical) {
+ return verticalIntersect(q1, p);
+ }
+ return horizontalIntersect(q1, p);
+}
+
+int horizontalIntersect(const Quadratic& quad, double left, double right,
+ double y, double tRange[2]) {
+ LineQuadraticIntersections q(quad, *((_Line*) 0), *((Intersections*) 0));
+ double rootVals[2];
+ int result = q.horizontalIntersect(y, rootVals);
+ int tCount = 0;
+ for (int index = 0; index < result; ++index) {
+ double x, y;
+ xy_at_t(quad, rootVals[index], x, y);
+ if (x < left || x > right) {
+ continue;
+ }
+ tRange[tCount++] = rootVals[index];
+ }
+ return tCount;
+}
+
+int horizontalIntersect(const Quadratic& quad, double left, double right, double y,
+ bool flipped, Intersections& intersections) {
+ LineQuadraticIntersections q(quad, *((_Line*) 0), intersections);
+ return q.horizontalIntersect(y, left, right, flipped);
+}
+
+int verticalIntersect(const Quadratic& quad, double top, double bottom, double x,
+ bool flipped, Intersections& intersections) {
+ LineQuadraticIntersections q(quad, *((_Line*) 0), intersections);
+ return q.verticalIntersect(x, top, bottom, flipped);
+}
+
+int intersect(const Quadratic& quad, const _Line& line, Intersections& i) {
+ LineQuadraticIntersections q(quad, line, i);
+ return q.intersect();
+}
+
+int intersectRay(const Quadratic& quad, const _Line& line, Intersections& i) {
+ LineQuadraticIntersections q(quad, line, i);
+ return q.intersectRay(i.fT[0]);
+}
diff --git a/experimental/Intersection/LineQuadraticIntersection_Test.cpp b/experimental/Intersection/LineQuadraticIntersection_Test.cpp
new file mode 100644
index 0000000000..69227c3a40
--- /dev/null
+++ b/experimental/Intersection/LineQuadraticIntersection_Test.cpp
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "CurveIntersection.h"
+#include "CurveUtilities.h"
+#include "EdgeWalker_Test.h"
+#include "Intersection_Tests.h"
+#include "Intersections.h"
+#include "TestUtilities.h"
+
+struct lineQuad {
+ Quadratic quad;
+ _Line line;
+ int result;
+ _Point expected[2];
+} lineQuadTests[] = {
+ // quad line results
+ {{{1, 1}, {2, 1}, {0, 2}}, {{0, 0}, {1, 1}}, 1, {{1, 1} }},
+ {{{0, 0}, {1, 1}, {3, 1}}, {{0, 0}, {3, 1}}, 2, {{0, 0}, {3, 1}}},
+ {{{2, 0}, {1, 1}, {2, 2}}, {{0, 0}, {0, 2}}, 0 },
+ {{{4, 0}, {0, 1}, {4, 2}}, {{3, 1}, {4, 1}}, 0, },
+ {{{0, 0}, {0, 1}, {1, 1}}, {{0, 1}, {1, 0}}, 1, {{.25, .75} }},
+};
+
+size_t lineQuadTests_count = sizeof(lineQuadTests) / sizeof(lineQuadTests[0]);
+
+const int firstLineQuadIntersectionTest = 0;
+
+static int doIntersect(Intersections& intersections, const Quadratic& quad, const _Line& line, bool& flipped) {
+ int result;
+ flipped = false;
+ if (line[0].x == line[1].x) {
+ double top = line[0].y;
+ double bottom = line[1].y;
+ flipped = top > bottom;
+ if (flipped) {
+ SkTSwap<double>(top, bottom);
+ }
+ result = verticalIntersect(quad, top, bottom, line[0].x, flipped, intersections);
+ } else if (line[0].y == line[1].y) {
+ double left = line[0].x;
+ double right = line[1].x;
+ flipped = left > right;
+ if (flipped) {
+ SkTSwap<double>(left, right);
+ }
+ result = horizontalIntersect(quad, left, right, line[0].y, flipped, intersections);
+ } else {
+ intersect(quad, line, intersections);
+ result = intersections.fUsed;
+ }
+ return result;
+}
+
+static struct oneLineQuad {
+ Quadratic quad;
+ _Line line;
+} oneOffs[] = {
+ {{{369.848602,145.680267}, {382.360413,121.298294}, {406.207703,121.298294}},
+ {{406.207703,121.298294}, {348.781738,123.864815}}}
+ };
+
+static size_t oneOffs_count = sizeof(oneOffs) / sizeof(oneOffs[0]);
+
+
+static void testOneOffs() {
+ Intersections intersections;
+ bool flipped = false;
+ for (size_t index = 0; index < oneOffs_count; ++index) {
+ const Quadratic& quad = oneOffs[index].quad;
+ const _Line& line = oneOffs[index].line;
+ int result = doIntersect(intersections, quad, line, flipped);
+ for (int inner = 0; inner < result; ++inner) {
+ double quadT = intersections.fT[0][inner];
+ double quadX, quadY;
+ xy_at_t(quad, quadT, quadX, quadY);
+ double lineT = intersections.fT[1][inner];
+ double lineX, lineY;
+ xy_at_t(line, lineT, lineX, lineY);
+ assert(approximately_equal(quadX, lineX)
+ && approximately_equal(quadY, lineY));
+ }
+ }
+}
+
+void LineQuadraticIntersection_Test() {
+ if (1) {
+ testOneOffs();
+ }
+ for (size_t index = firstLineQuadIntersectionTest; index < lineQuadTests_count; ++index) {
+ const Quadratic& quad = lineQuadTests[index].quad;
+ const _Line& line = lineQuadTests[index].line;
+ Quadratic reduce1;
+ _Line reduce2;
+ int order1 = reduceOrder(quad, reduce1);
+ int order2 = reduceOrder(line, reduce2);
+ if (order1 < 3) {
+ SkDebugf("%s [%d] quad order=%d\n", __FUNCTION__, (int) index, order1);
+ SkASSERT(0);
+ }
+ if (order2 < 2) {
+ SkDebugf("%s [%d] line order=%d\n", __FUNCTION__, (int) index, order2);
+ SkASSERT(0);
+ }
+ Intersections intersections;
+ bool flipped = false;
+ int result = doIntersect(intersections, quad, line, flipped);
+ SkASSERT(result == lineQuadTests[index].result);
+ if (!intersections.intersected()) {
+ continue;
+ }
+ for (int pt = 0; pt < result; ++pt) {
+ double tt1 = intersections.fT[0][pt];
+ SkASSERT(tt1 >= 0 && tt1 <= 1);
+ _Point t1, t2;
+ xy_at_t(quad, tt1, t1.x, t1.y);
+ double tt2 = intersections.fT[1][pt];
+ SkASSERT(tt2 >= 0 && tt2 <= 1);
+ xy_at_t(line, tt2, t2.x, t2.y);
+ if (!approximately_equal(t1.x, t2.x)) {
+ SkDebugf("%s [%d,%d] x!= t1=%1.9g (%1.9g,%1.9g) t2=%1.9g (%1.9g,%1.9g)\n",
+ __FUNCTION__, (int)index, pt, tt1, t1.x, t1.y, tt2, t2.x, t2.y);
+ SkASSERT(0);
+ }
+ if (!approximately_equal(t1.y, t2.y)) {
+ SkDebugf("%s [%d,%d] y!= t1=%1.9g (%1.9g,%1.9g) t2=%1.9g (%1.9g,%1.9g)\n",
+ __FUNCTION__, (int)index, pt, tt1, t1.x, t1.y, tt2, t2.x, t2.y);
+ SkASSERT(0);
+ }
+ if (!t1.approximatelyEqual(lineQuadTests[index].expected[0])
+ && (lineQuadTests[index].result == 1
+ || !t1.approximatelyEqual(lineQuadTests[index].expected[1]))) {
+ SkDebugf("%s t1=(%1.9g,%1.9g)\n", __FUNCTION__, t1.x, t1.y);
+ SkASSERT(0);
+ }
+ }
+ }
+}
+
+static void testLineIntersect(State4& state, const Quadratic& quad, const _Line& line,
+ const double x, const double y) {
+ char pathStr[1024];
+ bzero(pathStr, sizeof(pathStr));
+ char* str = pathStr;
+ str += sprintf(str, " path.moveTo(%1.9g, %1.9g);\n", quad[0].x, quad[0].y);
+ str += sprintf(str, " path.quadTo(%1.9g, %1.9g, %1.9g, %1.9g);\n", quad[1].x, quad[1].y, quad[2].x, quad[2].y);
+ str += sprintf(str, " path.moveTo(%1.9g, %1.9g);\n", line[0].x, line[0].y);
+ str += sprintf(str, " path.lineTo(%1.9g, %1.9g);\n", line[1].x, line[1].y);
+
+ Intersections intersections;
+ bool flipped = false;
+ int result = doIntersect(intersections, quad, line, flipped);
+ bool found = false;
+ for (int index = 0; index < result; ++index) {
+ double quadT = intersections.fT[0][index];
+ double quadX, quadY;
+ xy_at_t(quad, quadT, quadX, quadY);
+ double lineT = intersections.fT[1][index];
+ double lineX, lineY;
+ xy_at_t(line, lineT, lineX, lineY);
+ if (fabs(quadX - lineX) < FLT_EPSILON && fabs(quadY - lineY) < FLT_EPSILON
+ && fabs(x - lineX) < FLT_EPSILON && fabs(y - lineY) < FLT_EPSILON) {
+ found = true;
+ }
+ }
+ SkASSERT(found);
+ state.testsRun++;
+}
+
+
+// find a point on a quad by choosing a t from 0 to 1
+// create a vertical span above and below the point
+// verify that intersecting the vertical span and the quad returns t
+// verify that a vertical span starting at quad[0] intersects at t=0
+// verify that a vertical span starting at quad[2] intersects at t=1
+static void* testQuadLineIntersectMain(void* data)
+{
+ SkASSERT(data);
+ State4& state = *(State4*) data;
+ do {
+ int ax = state.a & 0x03;
+ int ay = state.a >> 2;
+ int bx = state.b & 0x03;
+ int by = state.b >> 2;
+ int cx = state.c & 0x03;
+ int cy = state.c >> 2;
+ Quadratic quad = {{ax, ay}, {bx, by}, {cx, cy}};
+ Quadratic reduced;
+ int order = reduceOrder(quad, reduced);
+ if (order < 3) {
+ continue; // skip degenerates
+ }
+ for (int tIndex = 0; tIndex <= 4; ++tIndex) {
+ double x, y;
+ xy_at_t(quad, tIndex / 4.0, x, y);
+ for (int h = -2; h <= 2; ++h) {
+ for (int v = -2; v <= 2; ++v) {
+ if (h == v && abs(h) != 1) {
+ continue;
+ }
+ _Line line = {{x - h, y - v}, {x, y}};
+ testLineIntersect(state, quad, line, x, y);
+ _Line line2 = {{x, y}, {x + h, y + v}};
+ testLineIntersect(state, quad, line2, x, y);
+ _Line line3 = {{x - h, y - v}, {x + h, y + v}};
+ testLineIntersect(state, quad, line3, x, y);
+ }
+ }
+ }
+ } while (runNextTestSet(state));
+ return NULL;
+}
+
+void QuadLineIntersectThreaded_Test(int& testsRun)
+{
+ SkDebugf("%s\n", __FUNCTION__);
+ const char testStr[] = "testQuadLineIntersect";
+ initializeTests(testStr, sizeof(testStr));
+ int testsStart = testsRun;
+ for (int a = 0; a < 16; ++a) {
+ for (int b = 0 ; b < 16; ++b) {
+ for (int c = 0 ; c < 16; ++c) {
+ testsRun += dispatchTest4(testQuadLineIntersectMain,
+ a, b, c, 0);
+ }
+ if (!gRunTestsInOneThread) SkDebugf(".");
+ }
+ if (!gRunTestsInOneThread) SkDebugf("%d", a);
+ }
+ testsRun += waitForCompletion();
+ SkDebugf("\n%s tests=%d total=%d\n", __FUNCTION__, testsRun - testsStart, testsRun);
+}
diff --git a/experimental/Intersection/LineUtilities.cpp b/experimental/Intersection/LineUtilities.cpp
new file mode 100644
index 0000000000..25bd88dc54
--- /dev/null
+++ b/experimental/Intersection/LineUtilities.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "CurveIntersection.h"
+#include "LineUtilities.h"
+
+bool implicitLine(const _Line& line, double& slope, double& axisIntercept) {
+ _Point delta;
+ tangent(line, delta);
+ bool moreHorizontal = fabs(delta.x) > fabs(delta.y);
+ if (moreHorizontal) {
+ slope = delta.y / delta.x;
+ axisIntercept = line[0].y - slope * line[0].x;
+ } else {
+ slope = delta.x / delta.y;
+ axisIntercept = line[0].x - slope * line[0].y;
+ }
+ return moreHorizontal;
+}
+
+int reduceOrder(const _Line& line, _Line& reduced) {
+ reduced[0] = line[0];
+ int different = line[0] != line[1];
+ reduced[1] = line[different];
+ return 1 + different;
+}
+
+void sub_divide(const _Line& line, double t1, double t2, _Line& dst) {
+ _Point delta;
+ tangent(line, delta);
+ dst[0].x = line[0].x - t1 * delta.x;
+ dst[0].y = line[0].y - t1 * delta.y;
+ dst[1].x = line[0].x - t2 * delta.x;
+ dst[1].y = line[0].y - t2 * delta.y;
+}
+
+// may have this below somewhere else already:
+// copying here because I thought it was clever
+
+// Copyright 2001, softSurfer (www.softsurfer.com)
+// This code may be freely used and modified for any purpose
+// providing that this copyright notice is included with it.
+// SoftSurfer makes no warranty for this code, and cannot be held
+// liable for any real or imagined damage resulting from its use.
+// Users of this code must verify correctness for their application.
+
+// Assume that a class is already given for the object:
+// Point with coordinates {float x, y;}
+//===================================================================
+
+// isLeft(): tests if a point is Left|On|Right of an infinite line.
+// Input: three points P0, P1, and P2
+// Return: >0 for P2 left of the line through P0 and P1
+// =0 for P2 on the line
+// <0 for P2 right of the line
+// See: the January 2001 Algorithm on Area of Triangles
+#if 0
+float isLeft( _Point P0, _Point P1, _Point P2 )
+{
+ return (float) ((P1.x - P0.x)*(P2.y - P0.y) - (P2.x - P0.x)*(P1.y - P0.y));
+}
+#endif
+
+double t_at(const _Line& line, const _Point& pt) {
+ double dx = line[1].x - line[0].x;
+ double dy = line[1].y - line[0].y;
+ if (fabs(dx) > fabs(dy)) {
+ if (approximately_zero(dx)) {
+ return 0;
+ }
+ return (pt.x - line[0].x) / dx;
+ }
+ if (approximately_zero(dy)) {
+ return 0;
+ }
+ return (pt.y - line[0].y) / dy;
+}
+
+static void setMinMax(double x, int flags, double& minX, double& maxX) {
+ if (minX > x && (flags & (kFindTopMin | kFindBottomMin))) {
+ minX = x;
+ }
+ if (maxX < x && (flags & (kFindTopMax | kFindBottomMax))) {
+ maxX = x;
+ }
+}
+
+void x_at(const _Point& p1, const _Point& p2, double top, double bottom,
+ int flags, double& minX, double& maxX) {
+ if (approximately_equal(p1.y, p2.y)) {
+ // It should be OK to bail early in this case. There's another edge
+ // which shares this end point which can intersect without failing to
+ // have a slope ... maybe
+ return;
+ }
+
+ // p2.x is always greater than p1.x -- the part of points (p1, p2) are
+ // moving from the start of the cubic towards its end.
+ // if p1.y < p2.y, minX can be affected
+ // if p1.y > p2.y, maxX can be affected
+ double slope = (p2.x - p1.x) / (p2.y - p1.y);
+ int topFlags = flags & (kFindTopMin | kFindTopMax);
+ if (topFlags && ((top <= p1.y && top >= p2.y)
+ || (top >= p1.y && top <= p2.y))) {
+ double x = p1.x + (top - p1.y) * slope;
+ setMinMax(x, topFlags, minX, maxX);
+ }
+ int bottomFlags = flags & (kFindBottomMin | kFindBottomMax);
+ if (bottomFlags && ((bottom <= p1.y && bottom >= p2.y)
+ || (bottom >= p1.y && bottom <= p2.y))) {
+ double x = p1.x + (bottom - p1.y) * slope;
+ setMinMax(x, bottomFlags, minX, maxX);
+ }
+}
+
+void xy_at_t(const _Line& line, double t, double& x, double& y) {
+ double one_t = 1 - t;
+ if (&x) {
+ x = one_t * line[0].x + t * line[1].x;
+ }
+ if (&y) {
+ y = one_t * line[0].y + t * line[1].y;
+ }
+}
diff --git a/experimental/Intersection/MiniSimplify_Test.cpp b/experimental/Intersection/MiniSimplify_Test.cpp
new file mode 100644
index 0000000000..3cb90ab927
--- /dev/null
+++ b/experimental/Intersection/MiniSimplify_Test.cpp
@@ -0,0 +1,99 @@
+#include "EdgeWalker_Test.h"
+#include "Intersection_Tests.h"
+#include "ShapeOps.h"
+
+bool gShowOriginal = true;
+
+struct curve {
+ SkPath::Verb verb;
+ SkPoint pts[4];
+};
+
+struct curve test1[] = {
+{SkPath::kQuad_Verb, {{366.608826f, 151.196014f}, {378.803101f, 136.674606f}, {398.164948f, 136.674606f}}},
+{SkPath::kLine_Verb, {{354.009216f, 208.816208f}, {393.291473f, 102.232819f}}},
+{SkPath::kQuad_Verb, {{359.978058f, 136.581512f}, {378.315979f, 136.581512f}, {388.322723f, 149.613556f}}},
+{SkPath::kQuad_Verb, {{364.390686f, 157.898193f}, {375.281769f, 136.674606f}, {396.039917f, 136.674606f}}},
+{SkPath::kLine_Verb, {{396.039917f, 136.674606f}, {350, 120}}},
+{SkPath::kDone_Verb}
+};
+
+struct curve test2[] = {
+{SkPath::kQuad_Verb, {{366.608826f, 151.196014f}, {378.803101f, 136.674606f}, {398.164948f, 136.674606f}}},
+{SkPath::kQuad_Verb, {{359.978058f, 136.581512f}, {378.315979f, 136.581512f}, {388.322723f, 149.613556f}}},
+{SkPath::kQuad_Verb, {{364.390686f, 157.898193f}, {375.281769f, 136.674606f}, {396.039917f, 136.674606f}}},
+{SkPath::kDone_Verb}
+};
+
+struct curve* testSet[] = {
+ test2,
+ test1
+};
+
+size_t testSet_count = sizeof(testSet) / sizeof(testSet[0]);
+
+static void construct() {
+ for (size_t idx = 0; idx < testSet_count; ++idx) {
+ const curve* test = testSet[idx];
+ SkPath path;
+ bool pathComplete = false;
+ bool first = true;
+ do {
+ if (first) {
+ path.moveTo(test->pts[0].fX, test->pts[0].fY);
+ first = false;
+ } else if (test->verb != SkPath::kDone_Verb) {
+ path.lineTo(test->pts[0].fX, test->pts[0].fY);
+ }
+ switch (test->verb) {
+ case SkPath::kDone_Verb:
+ pathComplete = true;
+ break;
+ case SkPath::kLine_Verb:
+ path.lineTo(test->pts[1].fX, test->pts[1].fY);
+ break;
+ case SkPath::kQuad_Verb:
+ path.quadTo(test->pts[1].fX, test->pts[1].fY, test->pts[2].fX, test->pts[2].fY);
+ break;
+ case SkPath::kCubic_Verb:
+ path.cubicTo(test->pts[1].fX, test->pts[1].fY, test->pts[2].fX, test->pts[2].fY, test->pts[3].fX, test->pts[3].fY);
+ break;
+ default:
+ SkASSERT(0);
+ }
+ test++;
+ } while (!pathComplete);
+ path.close();
+ if (gShowOriginal) {
+ showPath(path, NULL);
+ SkDebugf("simplified:\n");
+ }
+ testSimplifyx(path);
+ }
+}
+
+static void (*tests[])() = {
+ construct,
+};
+
+static const size_t testCount = sizeof(tests) / sizeof(tests[0]);
+
+static void (*firstTest)() = 0;
+static bool skipAll = false;
+
+void MiniSimplify_Test() {
+ if (skipAll) {
+ return;
+ }
+ size_t index = 0;
+ if (firstTest) {
+ while (index < testCount && tests[index] != firstTest) {
+ ++index;
+ }
+ }
+ bool firstTestComplete = false;
+ for ( ; index < testCount; ++index) {
+ (*tests[index])();
+ firstTestComplete = true;
+ }
+}
diff --git a/experimental/Intersection/QuadraticImplicit.cpp b/experimental/Intersection/QuadraticImplicit.cpp
new file mode 100644
index 0000000000..d892ae97e0
--- /dev/null
+++ b/experimental/Intersection/QuadraticImplicit.cpp
@@ -0,0 +1,230 @@
+// Another approach is to start with the implicit form of one curve and solve
+// (seek implicit coefficients in QuadraticParameter.cpp
+// by substituting in the parametric form of the other.
+// The downside of this approach is that early rejects are difficult to come by.
+// http://planetmath.org/encyclopedia/GaloisTheoreticDerivationOfTheQuarticFormula.html#step
+
+
+#include "CurveIntersection.h"
+#include "Intersections.h"
+#include "QuadraticParameterization.h"
+#include "QuarticRoot.h"
+#include "QuadraticUtilities.h"
+
+/* given the implicit form 0 = Ax^2 + Bxy + Cy^2 + Dx + Ey + F
+ * and given x = at^2 + bt + c (the parameterized form)
+ * y = dt^2 + et + f
+ * then
+ * 0 = A(at^2+bt+c)(at^2+bt+c)+B(at^2+bt+c)(dt^2+et+f)+C(dt^2+et+f)(dt^2+et+f)+D(at^2+bt+c)+E(dt^2+et+f)+F
+ */
+
+static int findRoots(const QuadImplicitForm& i, const Quadratic& q2, double roots[4]) {
+ double a, b, c;
+ set_abc(&q2[0].x, a, b, c);
+ double d, e, f;
+ set_abc(&q2[0].y, d, e, f);
+ const double t4 = i.x2() * a * a
+ + i.xy() * a * d
+ + i.y2() * d * d;
+ const double t3 = 2 * i.x2() * a * b
+ + i.xy() * (a * e + b * d)
+ + 2 * i.y2() * d * e;
+ const double t2 = i.x2() * (b * b + 2 * a * c)
+ + i.xy() * (c * d + b * e + a * f)
+ + i.y2() * (e * e + 2 * d * f)
+ + i.x() * a
+ + i.y() * d;
+ const double t1 = 2 * i.x2() * b * c
+ + i.xy() * (c * e + b * f)
+ + 2 * i.y2() * e * f
+ + i.x() * b
+ + i.y() * e;
+ const double t0 = i.x2() * c * c
+ + i.xy() * c * f
+ + i.y2() * f * f
+ + i.x() * c
+ + i.y() * f
+ + i.c();
+ return quarticRoots(t4, t3, t2, t1, t0, roots);
+}
+
+static void addValidRoots(const double roots[4], const int count, const int side, Intersections& i) {
+ int index;
+ for (index = 0; index < count; ++index) {
+ if (!approximately_zero_or_more(roots[index]) || !approximately_one_or_less(roots[index])) {
+ continue;
+ }
+ double t = 1 - roots[index];
+ if (approximately_less_than_zero(t)) {
+ t = 0;
+ } else if (approximately_greater_than_one(t)) {
+ t = 1;
+ }
+ i.insertOne(t, side);
+ }
+}
+
+static bool onlyEndPtsInCommon(const Quadratic& q1, const Quadratic& q2, Intersections& i) {
+// the idea here is to see at minimum do a quick reject by rotating all points
+// to either side of the line formed by connecting the endpoints
+// if the opposite curves points are on the line or on the other side, the
+// curves at most intersect at the endpoints
+ for (int oddMan = 0; oddMan < 3; ++oddMan) {
+ const _Point* endPt[2];
+ for (int opp = 1; opp < 3; ++opp) {
+ int end = oddMan ^ opp;
+ if (end == 3) {
+ end = opp;
+ }
+ endPt[opp - 1] = &q1[end];
+ }
+ double origX = endPt[0]->x;
+ double origY = endPt[0]->y;
+ double adj = endPt[1]->x - origX;
+ double opp = endPt[1]->y - origY;
+ double sign = (q1[oddMan].y - origY) * adj - (q1[oddMan].x - origX) * opp;
+ assert(!approximately_zero(sign));
+ for (int n = 0; n < 3; ++n) {
+ double test = (q2[n].y - origY) * adj - (q2[n].x - origX) * opp;
+ if (test * sign > 0) {
+ goto tryNextHalfPlane;
+ }
+ }
+ for (int i1 = 0; i1 < 3; i1 += 2) {
+ for (int i2 = 0; i2 < 3; i2 += 2) {
+ if (q1[i1] == q2[i2]) {
+ i.insert(i1 >> 1, i2 >> 1);
+ }
+ }
+ }
+ assert(i.fUsed < 3);
+ return true;
+tryNextHalfPlane:
+ ;
+ }
+ return false;
+}
+
+bool intersect2(const Quadratic& q1, const Quadratic& q2, Intersections& i) {
+ // if the quads share an end point, check to see if they overlap
+
+ if (onlyEndPtsInCommon(q1, q2, i)) {
+ return i.intersected();
+ }
+ QuadImplicitForm i1(q1);
+ QuadImplicitForm i2(q2);
+ if (i1.implicit_match(i2)) {
+ // FIXME: compute T values
+ // compute the intersections of the ends to find the coincident span
+ bool useVertical = fabs(q1[0].x - q1[2].x) < fabs(q1[0].y - q1[2].y);
+ double t;
+ if ((t = axialIntersect(q1, q2[0], useVertical)) >= 0) {
+ i.addCoincident(t, 0);
+ }
+ if ((t = axialIntersect(q1, q2[2], useVertical)) >= 0) {
+ i.addCoincident(t, 1);
+ }
+ useVertical = fabs(q2[0].x - q2[2].x) < fabs(q2[0].y - q2[2].y);
+ if ((t = axialIntersect(q2, q1[0], useVertical)) >= 0) {
+ i.addCoincident(0, t);
+ }
+ if ((t = axialIntersect(q2, q1[2], useVertical)) >= 0) {
+ i.addCoincident(1, t);
+ }
+ assert(i.fCoincidentUsed <= 2);
+ return i.fCoincidentUsed > 0;
+ }
+ double roots1[4], roots2[4];
+ int rootCount = findRoots(i2, q1, roots1);
+ // OPTIMIZATION: could short circuit here if all roots are < 0 or > 1
+#ifndef NDEBUG
+ int rootCount2 =
+#endif
+ findRoots(i1, q2, roots2);
+ assert(rootCount == rootCount2);
+ addValidRoots(roots1, rootCount, 0, i);
+ addValidRoots(roots2, rootCount, 1, i);
+ if (i.insertBalanced() && i.fUsed <= 1) {
+ if (i.fUsed == 1) {
+ _Point xy1, xy2;
+ xy_at_t(q1, i.fT[0][0], xy1.x, xy1.y);
+ xy_at_t(q2, i.fT[1][0], xy2.x, xy2.y);
+ if (!xy1.approximatelyEqual(xy2)) {
+ --i.fUsed;
+ --i.fUsed2;
+ }
+ }
+ return i.intersected();
+ }
+ _Point pts[4];
+ bool matches[4];
+ int flipCheck[4];
+ int closest[4];
+ double dist[4];
+ int index, ndex2;
+ int flipIndex = 0;
+ for (ndex2 = 0; ndex2 < i.fUsed2; ++ndex2) {
+ xy_at_t(q2, i.fT[1][ndex2], pts[ndex2].x, pts[ndex2].y);
+ matches[ndex2] = false;
+ }
+ for (index = 0; index < i.fUsed; ++index) {
+ _Point xy;
+ xy_at_t(q1, i.fT[0][index], xy.x, xy.y);
+ dist[index] = DBL_MAX;
+ closest[index] = -1;
+ for (ndex2 = 0; ndex2 < i.fUsed2; ++ndex2) {
+ if (!pts[ndex2].approximatelyEqual(xy)) {
+ continue;
+ }
+ double dx = pts[ndex2].x - xy.x;
+ double dy = pts[ndex2].y - xy.y;
+ double distance = dx * dx + dy * dy;
+ if (dist[index] <= distance) {
+ continue;
+ }
+ for (int outer = 0; outer < index; ++outer) {
+ if (closest[outer] != ndex2) {
+ continue;
+ }
+ if (dist[outer] < distance) {
+ goto next;
+ }
+ closest[outer] = -1;
+ }
+ dist[index] = distance;
+ closest[index] = ndex2;
+ next:
+ ;
+ }
+ }
+ for (index = 0; index < i.fUsed; ) {
+ for (ndex2 = 0; ndex2 < i.fUsed2; ++ndex2) {
+ if (closest[index] == ndex2) {
+ assert(flipIndex < 4);
+ flipCheck[flipIndex++] = ndex2;
+ matches[ndex2] = true;
+ goto next2;
+ }
+ }
+ if (--i.fUsed > index) {
+ memmove(&i.fT[0][index], &i.fT[0][index + 1], (i.fUsed - index) * sizeof(i.fT[0][0]));
+ memmove(&closest[index], &closest[index + 1], (i.fUsed - index) * sizeof(closest[0]));
+ continue;
+ }
+ next2:
+ ++index;
+ }
+ for (ndex2 = 0; ndex2 < i.fUsed2; ) {
+ if (!matches[ndex2]) {
+ if (--i.fUsed2 > ndex2) {
+ memmove(&i.fT[1][ndex2], &i.fT[1][ndex2 + 1], (i.fUsed2 - ndex2) * sizeof(i.fT[1][0]));
+ memmove(&matches[ndex2], &matches[ndex2 + 1], (i.fUsed2 - ndex2) * sizeof(matches[0]));
+ continue;
+ }
+ }
+ ++ndex2;
+ }
+ i.fFlip = i.fUsed >= 2 && flipCheck[0] > flipCheck[1];
+ assert(i.insertBalanced());
+ return i.intersected();
+}
diff --git a/experimental/Intersection/QuadraticIntersection_Test.cpp b/experimental/Intersection/QuadraticIntersection_Test.cpp
new file mode 100644
index 0000000000..bab6c73718
--- /dev/null
+++ b/experimental/Intersection/QuadraticIntersection_Test.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "CurveIntersection.h"
+#include "CurveUtilities.h"
+#include "Intersection_Tests.h"
+#include "Intersections.h"
+#include "QuadraticIntersection_TestData.h"
+#include "TestUtilities.h"
+#include "SkTypes.h"
+
+const int firstQuadIntersectionTest = 9;
+
+static void standardTestCases() {
+ for (size_t index = firstQuadIntersectionTest; index < quadraticTests_count; ++index) {
+ const Quadratic& quad1 = quadraticTests[index][0];
+ const Quadratic& quad2 = quadraticTests[index][1];
+ Quadratic reduce1, reduce2;
+ int order1 = reduceOrder(quad1, reduce1);
+ int order2 = reduceOrder(quad2, reduce2);
+ if (order1 < 3) {
+ printf("[%d] quad1 order=%d\n", (int) index, order1);
+ }
+ if (order2 < 3) {
+ printf("[%d] quad2 order=%d\n", (int) index, order2);
+ }
+ if (order1 == 3 && order2 == 3) {
+ Intersections intersections, intersections2;
+ intersect(reduce1, reduce2, intersections);
+ intersect2(reduce1, reduce2, intersections2);
+ SkASSERT(intersections.used() == intersections2.used());
+ if (intersections.intersected()) {
+ for (int pt = 0; pt < intersections.used(); ++pt) {
+ double tt1 = intersections.fT[0][pt];
+ double tx1, ty1;
+ xy_at_t(quad1, tt1, tx1, ty1);
+ double tt2 = intersections.fT[1][pt];
+ double tx2, ty2;
+ xy_at_t(quad2, tt2, tx2, ty2);
+ if (!approximately_equal(tx1, tx2)) {
+ printf("%s [%d,%d] x!= t1=%g (%g,%g) t2=%g (%g,%g)\n",
+ __FUNCTION__, (int)index, pt, tt1, tx1, ty1, tt2, tx2, ty2);
+ }
+ if (!approximately_equal(ty1, ty2)) {
+ printf("%s [%d,%d] y!= t1=%g (%g,%g) t2=%g (%g,%g)\n",
+ __FUNCTION__, (int)index, pt, tt1, tx1, ty1, tt2, tx2, ty2);
+ }
+ tt1 = intersections2.fT[0][pt];
+ SkASSERT(approximately_equal(intersections.fT[0][pt], tt1));
+ tt2 = intersections2.fT[1][pt];
+ SkASSERT(approximately_equal(intersections.fT[1][pt], tt2));
+ }
+ }
+ }
+ }
+}
+
+static const Quadratic testSet[] = {
+{{0, 0}, {1, 0}, {0, 3}},
+{{1, 0}, {0, 1}, {1, 1}},
+{{369.848602,145.680267}, {382.360413,121.298294}, {406.207703,121.298294}},
+{{369.961151,137.980698}, {383.970093,121.298294}, {406.213287,121.298294}},
+{{353.2948,194.351074}, {353.2948,173.767563}, {364.167572,160.819855}},
+{{360.416077,166.795715}, {370.126831,147.872162}, {388.635406,147.872162}},
+{{406.236359,121.254936}, {409.445679,121.254936}, {412.975952,121.789818}},
+{{406.235992,121.254936}, {425.705902,121.254936}, {439.71994,137.087616}},
+
+{{369.8543701171875, 145.66734313964844}, {382.36788940429688, 121.28203582763672}, {406.21844482421875, 121.28203582763672}},
+{{369.96469116210938, 137.96672058105469}, {383.97555541992188, 121.28203582763672}, {406.2218017578125, 121.28203582763672}},
+
+ {{369.850525, 145.675964}, {382.362915, 121.29287}, {406.211273, 121.29287}},
+ {{369.962311, 137.976044}, {383.971893, 121.29287}, {406.216125, 121.29287}},
+
+ {{400.121704, 149.468719}, {391.949493, 161.037186}, {391.949493, 181.202423}},
+ {{391.946747, 181.839218}, {391.946747, 155.62442}, {406.115479, 138.855438}},
+ {{360.048828125, 229.2578125}, {360.048828125, 224.4140625}, {362.607421875, 221.3671875}},
+ {{362.607421875, 221.3671875}, {365.166015625, 218.3203125}, {369.228515625, 218.3203125}},
+ {{8, 8}, {10, 10}, {8, -10}},
+ {{8, 8}, {12, 12}, {14, 4}},
+ {{8, 8}, {9, 9}, {10, 8}}
+};
+
+const size_t testSetCount = sizeof(testSet) / sizeof(testSet[0]);
+
+static void oneOffTest() {
+ for (size_t outer = 0; outer < testSetCount - 1; ++outer) {
+ for (size_t inner = outer + 1; inner < testSetCount; ++inner) {
+ const Quadratic& quad1 = testSet[outer];
+ const Quadratic& quad2 = testSet[inner];
+ double tt1, tt2;
+ Intersections intersections2;
+ intersect2(quad1, quad2, intersections2);
+ for (int pt = 0; pt < intersections2.used(); ++pt) {
+ tt1 = intersections2.fT[0][pt];
+ double tx1, ty1;
+ xy_at_t(quad1, tt1, tx1, ty1);
+ int pt2 = intersections2.fFlip ? intersections2.used() - pt - 1 : pt;
+ tt2 = intersections2.fT[1][pt2];
+ double tx2, ty2;
+ xy_at_t(quad2, tt2, tx2, ty2);
+ if (!approximately_equal(tx1, tx2)) {
+ SkDebugf("%s [%d,%d] x!= t1=%g (%g,%g) t2=%g (%g,%g)\n",
+ __FUNCTION__, (int)index, pt, tt1, tx1, ty1, tt2, tx2, ty2);
+ SkASSERT(0);
+ }
+ if (!approximately_equal(ty1, ty2)) {
+ SkDebugf("%s [%d,%d] y!= t1=%g (%g,%g) t2=%g (%g,%g)\n",
+ __FUNCTION__, (int)index, pt, tt1, tx1, ty1, tt2, tx2, ty2);
+ SkASSERT(0);
+ }
+ SkDebugf("%s [%d][%d] t1=%1.9g (%1.9g, %1.9g) t2=%1.9g\n", __FUNCTION__,
+ outer, inner, tt1, tx1, tx2, tt2);
+ }
+ }
+ }
+}
+
+static const Quadratic coincidentTestSet[] = {
+ {{369.850525, 145.675964}, {382.362915, 121.29287}, {406.211273, 121.29287}},
+ {{369.850525, 145.675964}, {382.362915, 121.29287}, {406.211273, 121.29287}},
+ {{8, 8}, {10, 10}, {8, -10}},
+ {{8, -10}, {10, 10}, {8, 8}},
+};
+
+const size_t coincidentTestSetCount = sizeof(coincidentTestSet) / sizeof(coincidentTestSet[0]);
+
+static void coincidentTest() {
+ for (size_t testIndex = 0; testIndex < coincidentTestSetCount - 1; testIndex += 2) {
+ const Quadratic& quad1 = coincidentTestSet[testIndex];
+ const Quadratic& quad2 = coincidentTestSet[testIndex + 1];
+ Intersections intersections2;
+ intersect2(quad1, quad2, intersections2);
+ SkASSERT(intersections2.coincidentUsed() == 2);
+ for (int pt = 0; pt < intersections2.coincidentUsed(); ++pt) {
+ double tt1 = intersections2.fT[0][pt];
+ double tt2 = intersections2.fT[1][pt];
+ SkASSERT(approximately_equal(1, tt1) || approximately_zero(tt1));
+ SkASSERT(approximately_equal(1, tt2) || approximately_zero(tt2));
+ }
+ }
+}
+
+void QuadraticIntersection_Test() {
+ oneOffTest();
+ coincidentTest();
+ standardTestCases();
+}
diff --git a/experimental/Intersection/QuadraticReduceOrder.cpp b/experimental/Intersection/QuadraticReduceOrder.cpp
new file mode 100644
index 0000000000..b68a68b3ac
--- /dev/null
+++ b/experimental/Intersection/QuadraticReduceOrder.cpp
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "CurveIntersection.h"
+#include "Extrema.h"
+#include "IntersectionUtilities.h"
+#include "LineParameters.h"
+
+static double interp_quad_coords(double a, double b, double c, double t)
+{
+ double ab = interp(a, b, t);
+ double bc = interp(b, c, t);
+ return interp(ab, bc, t);
+}
+
+static int coincident_line(const Quadratic& quad, Quadratic& reduction) {
+ reduction[0] = reduction[1] = quad[0];
+ return 1;
+}
+
+static int vertical_line(const Quadratic& quad, Quadratic& reduction) {
+ double tValue;
+ reduction[0] = quad[0];
+ reduction[1] = quad[2];
+ int smaller = reduction[1].y > reduction[0].y;
+ int larger = smaller ^ 1;
+ if (findExtrema(quad[0].y, quad[1].y, quad[2].y, &tValue)) {
+ double yExtrema = interp_quad_coords(quad[0].y, quad[1].y, quad[2].y, tValue);
+ if (reduction[smaller].y > yExtrema) {
+ reduction[smaller].y = yExtrema;
+ } else if (reduction[larger].y < yExtrema) {
+ reduction[larger].y = yExtrema;
+ }
+ }
+ return 2;
+}
+
+static int horizontal_line(const Quadratic& quad, Quadratic& reduction) {
+ double tValue;
+ reduction[0] = quad[0];
+ reduction[1] = quad[2];
+ int smaller = reduction[1].x > reduction[0].x;
+ int larger = smaller ^ 1;
+ if (findExtrema(quad[0].x, quad[1].x, quad[2].x, &tValue)) {
+ double xExtrema = interp_quad_coords(quad[0].x, quad[1].x, quad[2].x, tValue);
+ if (reduction[smaller].x > xExtrema) {
+ reduction[smaller].x = xExtrema;
+ } else if (reduction[larger].x < xExtrema) {
+ reduction[larger].x = xExtrema;
+ }
+ }
+ return 2;
+}
+
+static int check_linear(const Quadratic& quad, Quadratic& reduction,
+ int minX, int maxX, int minY, int maxY) {
+ int startIndex = 0;
+ int endIndex = 2;
+ while (quad[startIndex].approximatelyEqual(quad[endIndex])) {
+ --endIndex;
+ if (endIndex == 0) {
+ printf("%s shouldn't get here if all four points are about equal", __FUNCTION__);
+ assert(0);
+ }
+ }
+ if (!isLinear(quad, startIndex, endIndex)) {
+ return 0;
+ }
+ // four are colinear: return line formed by outside
+ reduction[0] = quad[0];
+ reduction[1] = quad[2];
+ int sameSide;
+ bool useX = quad[maxX].x - quad[minX].x >= quad[maxY].y - quad[minY].y;
+ if (useX) {
+ sameSide = sign(quad[0].x - quad[1].x) + sign(quad[2].x - quad[1].x);
+ } else {
+ sameSide = sign(quad[0].y - quad[1].y) + sign(quad[2].y - quad[1].y);
+ }
+ if ((sameSide & 3) != 2) {
+ return 2;
+ }
+ double tValue;
+ int root;
+ if (useX) {
+ root = findExtrema(quad[0].x, quad[1].x, quad[2].x, &tValue);
+ } else {
+ root = findExtrema(quad[0].y, quad[1].y, quad[2].y, &tValue);
+ }
+ if (root) {
+ _Point extrema;
+ extrema.x = interp_quad_coords(quad[0].x, quad[1].x, quad[2].x, tValue);
+ extrema.y = interp_quad_coords(quad[0].x, quad[1].x, quad[2].x, tValue);
+ // sameSide > 0 means mid is smaller than either [0] or [2], so replace smaller
+ int replace;
+ if (useX) {
+ if (extrema.x < quad[0].x ^ extrema.x < quad[2].x) {
+ return 2;
+ }
+ replace = (extrema.x < quad[0].x | extrema.x < quad[2].x)
+ ^ (quad[0].x < quad[2].x);
+ } else {
+ if (extrema.y < quad[0].y ^ extrema.y < quad[2].y) {
+ return 2;
+ }
+ replace = (extrema.y < quad[0].y | extrema.y < quad[2].y)
+ ^ (quad[0].y < quad[2].y);
+ }
+ reduction[replace] = extrema;
+ }
+ return 2;
+}
+
+bool isLinear(const Quadratic& quad, int startIndex, int endIndex) {
+ LineParameters lineParameters;
+ lineParameters.quadEndPoints(quad, startIndex, endIndex);
+ // FIXME: maybe it's possible to avoid this and compare non-normalized
+ lineParameters.normalize();
+ double distance = lineParameters.controlPtDistance(quad);
+ return approximately_zero(distance);
+}
+
+// reduce to a quadratic or smaller
+// look for identical points
+// look for all four points in a line
+ // note that three points in a line doesn't simplify a cubic
+// look for approximation with single quadratic
+ // save approximation with multiple quadratics for later
+int reduceOrder(const Quadratic& quad, Quadratic& reduction) {
+ int index, minX, maxX, minY, maxY;
+ int minXSet, minYSet;
+ minX = maxX = minY = maxY = 0;
+ minXSet = minYSet = 0;
+ for (index = 1; index < 3; ++index) {
+ if (quad[minX].x > quad[index].x) {
+ minX = index;
+ }
+ if (quad[minY].y > quad[index].y) {
+ minY = index;
+ }
+ if (quad[maxX].x < quad[index].x) {
+ maxX = index;
+ }
+ if (quad[maxY].y < quad[index].y) {
+ maxY = index;
+ }
+ }
+ for (index = 0; index < 3; ++index) {
+ if (approximately_equal(quad[index].x, quad[minX].x)) {
+ minXSet |= 1 << index;
+ }
+ if (approximately_equal(quad[index].y, quad[minY].y)) {
+ minYSet |= 1 << index;
+ }
+ }
+ if (minXSet == 0x7) { // test for vertical line
+ if (minYSet == 0x7) { // return 1 if all four are coincident
+ return coincident_line(quad, reduction);
+ }
+ return vertical_line(quad, reduction);
+ }
+ if (minYSet == 0xF) { // test for horizontal line
+ return horizontal_line(quad, reduction);
+ }
+ int result = check_linear(quad, reduction, minX, maxX, minY, maxY);
+ if (result) {
+ return result;
+ }
+ memcpy(reduction, quad, sizeof(Quadratic));
+ return 3;
+}
diff --git a/experimental/Intersection/QuarticRoot.cpp b/experimental/Intersection/QuarticRoot.cpp
new file mode 100644
index 0000000000..66ce3bf415
--- /dev/null
+++ b/experimental/Intersection/QuarticRoot.cpp
@@ -0,0 +1,295 @@
+// from http://tog.acm.org/resources/GraphicsGems/gems/Roots3And4.c
+/*
+ * Roots3And4.c
+ *
+ * Utility functions to find cubic and quartic roots,
+ * coefficients are passed like this:
+ *
+ * c[0] + c[1]*x + c[2]*x^2 + c[3]*x^3 + c[4]*x^4 = 0
+ *
+ * The functions return the number of non-complex roots and
+ * put the values into the s array.
+ *
+ * Author: Jochen Schwarze (schwarze@isa.de)
+ *
+ * Jan 26, 1990 Version for Graphics Gems
+ * Oct 11, 1990 Fixed sign problem for negative q's in SolveQuartic
+ * (reported by Mark Podlipec),
+ * Old-style function definitions,
+ * IsZero() as a macro
+ * Nov 23, 1990 Some systems do not declare acos() and cbrt() in
+ * <math.h>, though the functions exist in the library.
+ * If large coefficients are used, EQN_EPS should be
+ * reduced considerably (e.g. to 1E-30), results will be
+ * correct but multiple roots might be reported more
+ * than once.
+ */
+
+#include <math.h>
+#include "CubicUtilities.h"
+#include "QuarticRoot.h"
+
+const double PI = 4 * atan(1);
+
+// unlike quadraticRoots in QuadraticUtilities.cpp, this does not discard
+// real roots <= 0 or >= 1
+static int quadraticRootsX(const double A, const double B, const double C,
+ double s[2]) {
+ if (approximately_zero(A)) {
+ if (approximately_zero(B)) {
+ s[0] = 0;
+ return C == 0;
+ }
+ s[0] = -C / B;
+ return 1;
+ }
+ /* normal form: x^2 + px + q = 0 */
+ const double p = B / (2 * A);
+ const double q = C / A;
+ double D = p * p - q;
+ if (D < 0) {
+ if (approximately_positive_squared(D)) {
+ D = 0;
+ } else {
+ return 0;
+ }
+ }
+ double sqrt_D = sqrt(D);
+ if (approximately_less_than_zero(sqrt_D)) {
+ s[0] = -p;
+ return 1;
+ }
+ s[0] = sqrt_D - p;
+ s[1] = -sqrt_D - p;
+ return 2;
+}
+
+#define USE_GEMS 0
+#if USE_GEMS
+// unlike cubicRoots in CubicUtilities.cpp, this does not discard
+// real roots <= 0 or >= 1
+static int cubicRootsX(const double A, const double B, const double C,
+ const double D, double s[3]) {
+ int num;
+ /* normal form: x^3 + Ax^2 + Bx + C = 0 */
+ const double invA = 1 / A;
+ const double a = B * invA;
+ const double b = C * invA;
+ const double c = D * invA;
+ /* substitute x = y - a/3 to eliminate quadric term:
+ x^3 +px + q = 0 */
+ const double a2 = a * a;
+ const double Q = (-a2 + b * 3) / 9;
+ const double R = (2 * a2 * a - 9 * a * b + 27 * c) / 54;
+ /* use Cardano's formula */
+ const double Q3 = Q * Q * Q;
+ const double R2plusQ3 = R * R + Q3;
+ if (approximately_zero(R2plusQ3)) {
+ if (approximately_zero(R)) {/* one triple solution */
+ s[0] = 0;
+ num = 1;
+ } else { /* one single and one double solution */
+
+ double u = cube_root(-R);
+ s[0] = 2 * u;
+ s[1] = -u;
+ num = 2;
+ }
+ }
+ else if (R2plusQ3 < 0) { /* Casus irreducibilis: three real solutions */
+ const double theta = acos(-R / sqrt(-Q3)) / 3;
+ const double _2RootQ = 2 * sqrt(-Q);
+ s[0] = _2RootQ * cos(theta);
+ s[1] = -_2RootQ * cos(theta + PI / 3);
+ s[2] = -_2RootQ * cos(theta - PI / 3);
+ num = 3;
+ } else { /* one real solution */
+ const double sqrt_D = sqrt(R2plusQ3);
+ const double u = cube_root(sqrt_D - R);
+ const double v = -cube_root(sqrt_D + R);
+ s[0] = u + v;
+ num = 1;
+ }
+ /* resubstitute */
+ const double sub = a / 3;
+ for (int i = 0; i < num; ++i) {
+ s[i] -= sub;
+ }
+ return num;
+}
+#else
+
+static int cubicRootsX(double A, double B, double C, double D, double s[3]) {
+ if (approximately_zero(A)) { // we're just a quadratic
+ return quadraticRootsX(B, C, D, s);
+ }
+ if (approximately_zero(D)) { // 0 is one root
+ int num = quadraticRootsX(A, B, C, s);
+ for (int i = 0; i < num; ++i) {
+ if (approximately_zero(s[i])) {
+ return num;
+ }
+ }
+ s[num++] = 0;
+ return num;
+ }
+ if (approximately_zero(A + B + C + D)) { // 1 is one root
+ int num = quadraticRootsX(A, A + B, -D, s);
+ for (int i = 0; i < num; ++i) {
+ if (approximately_equal(s[i], 1)) {
+ return num;
+ }
+ }
+ s[num++] = 1;
+ return num;
+ }
+ double a, b, c;
+ {
+ double invA = 1 / A;
+ a = B * invA;
+ b = C * invA;
+ c = D * invA;
+ }
+ double a2 = a * a;
+ double Q = (a2 - b * 3) / 9;
+ double R = (2 * a2 * a - 9 * a * b + 27 * c) / 54;
+ double Q3 = Q * Q * Q;
+ double R2MinusQ3 = R * R - Q3;
+ double adiv3 = a / 3;
+ double r;
+ double* roots = s;
+
+ if (approximately_zero_squared(R2MinusQ3)) {
+ if (approximately_zero(R)) {/* one triple solution */
+ *roots++ = -adiv3;
+ } else { /* one single and one double solution */
+
+ double u = cube_root(-R);
+ *roots++ = 2 * u - adiv3;
+ *roots++ = -u - adiv3;
+ }
+ }
+ else if (R2MinusQ3 < 0) // we have 3 real roots
+ {
+ double theta = acos(R / sqrt(Q3));
+ double neg2RootQ = -2 * sqrt(Q);
+
+ r = neg2RootQ * cos(theta / 3) - adiv3;
+ *roots++ = r;
+
+ r = neg2RootQ * cos((theta + 2 * PI) / 3) - adiv3;
+ *roots++ = r;
+
+ r = neg2RootQ * cos((theta - 2 * PI) / 3) - adiv3;
+ *roots++ = r;
+ }
+ else // we have 1 real root
+ {
+ double A = fabs(R) + sqrt(R2MinusQ3);
+ A = cube_root(A);
+ if (R > 0) {
+ A = -A;
+ }
+ if (A != 0) {
+ A += Q / A;
+ }
+ r = A - adiv3;
+ *roots++ = r;
+ }
+ return (int)(roots - s);
+}
+#endif
+
+int quarticRoots(const double A, const double B, const double C, const double D,
+ const double E, double s[4]) {
+ if (approximately_zero(A)) {
+ if (approximately_zero(B)) {
+ return quadraticRootsX(C, D, E, s);
+ }
+ return cubicRootsX(B, C, D, E, s);
+ }
+ int num;
+ int i;
+ if (approximately_zero(E)) { // 0 is one root
+ num = cubicRootsX(A, B, C, D, s);
+ for (i = 0; i < num; ++i) {
+ if (approximately_zero(s[i])) {
+ return num;
+ }
+ }
+ s[num++] = 0;
+ return num;
+ }
+ if (approximately_zero_squared(A + B + C + D + E)) { // 1 is one root
+ num = cubicRootsX(A, A + B, -(D + E), -E, s); // note that -C==A+B+D+E
+ for (i = 0; i < num; ++i) {
+ if (approximately_equal(s[i], 1)) {
+ return num;
+ }
+ }
+ s[num++] = 1;
+ return num;
+ }
+ double u, v;
+ /* normal form: x^4 + Ax^3 + Bx^2 + Cx + D = 0 */
+ const double invA = 1 / A;
+ const double a = B * invA;
+ const double b = C * invA;
+ const double c = D * invA;
+ const double d = E * invA;
+ /* substitute x = y - a/4 to eliminate cubic term:
+ x^4 + px^2 + qx + r = 0 */
+ const double a2 = a * a;
+ const double p = -3 * a2 / 8 + b;
+ const double q = a2 * a / 8 - a * b / 2 + c;
+ const double r = -3 * a2 * a2 / 256 + a2 * b / 16 - a * c / 4 + d;
+ if (approximately_zero(r)) {
+ /* no absolute term: y(y^3 + py + q) = 0 */
+ num = cubicRootsX(1, 0, p, q, s);
+ s[num++] = 0;
+ } else {
+ /* solve the resolvent cubic ... */
+ (void) cubicRootsX(1, -p / 2, -r, r * p / 2 - q * q / 8, s);
+ /* ... and take the one real solution ... */
+ const double z = s[0];
+ /* ... to build two quadric equations */
+ u = z * z - r;
+ v = 2 * z - p;
+ if (approximately_zero(u)) {
+ u = 0;
+ } else if (u > 0) {
+ u = sqrt(u);
+ } else {
+ return 0;
+ }
+ if (approximately_zero(v)) {
+ v = 0;
+ } else if (v > 0) {
+ v = sqrt(v);
+ } else {
+ return 0;
+ }
+ num = quadraticRootsX(1, q < 0 ? -v : v, z - u, s);
+ num += quadraticRootsX(1, q < 0 ? v : -v, z + u, s + num);
+ }
+ // eliminate duplicates
+ for (i = 0; i < num - 1; ++i) {
+ for (int j = i + 1; j < num; ) {
+ if (approximately_equal(s[i], s[j])) {
+ if (j < --num) {
+ s[j] = s[num];
+ }
+ } else {
+ ++j;
+ }
+ }
+ }
+ /* resubstitute */
+ const double sub = a / 4;
+ for (i = 0; i < num; ++i) {
+ s[i] -= sub;
+ }
+ return num;
+}
+
+
diff --git a/experimental/Intersection/ShapeOpRect4x4_Test.cpp b/experimental/Intersection/ShapeOpRect4x4_Test.cpp
new file mode 100644
index 0000000000..d149377015
--- /dev/null
+++ b/experimental/Intersection/ShapeOpRect4x4_Test.cpp
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "EdgeWalker_Test.h"
+#include "Intersection_Tests.h"
+#include "ShapeOps.h"
+
+// four rects, of four sizes
+// for 3 smaller sizes, tall, wide
+ // top upper mid lower bottom aligned (3 bits, 5 values)
+ // same with x (3 bits, 5 values)
+// not included, square, tall, wide (2 bits)
+// cw or ccw (1 bit)
+
+static void* testShapeOps4x4RectsMain(void* data)
+{
+ SkASSERT(data);
+ State4& state = *(State4*) data;
+ char pathStr[1024]; // gdb: set print elements 400
+ bzero(pathStr, sizeof(pathStr));
+ do {
+ for (int a = 0 ; a < 6; ++a) {
+ for (int b = a + 1 ; b < 7; ++b) {
+ for (int c = 0 ; c < 6; ++c) {
+ for (int d = c + 1 ; d < 7; ++d) {
+ for (int op = 0 ; op < kShapeOp_Count; ++op) {
+ for (int e = SkPath::kWinding_FillType ; e <= SkPath::kEvenOdd_FillType; ++e) {
+ for (int f = SkPath::kWinding_FillType ; f <= SkPath::kEvenOdd_FillType; ++f) {
+ SkPath pathA, pathB;
+ char* str = pathStr;
+ pathA.setFillType((SkPath::FillType) e);
+ str += sprintf(str, " path.setFillType(SkPath::k%s_FillType);\n",
+ e == SkPath::kWinding_FillType ? "Winding" : e == SkPath::kEvenOdd_FillType
+ ? "EvenOdd" : "?UNDEFINED");
+ pathA.addRect(state.a, state.a, state.b, state.b, SkPath::kCW_Direction);
+ str += sprintf(str, " path.addRect(%d, %d, %d, %d,"
+ " SkPath::kCW_Direction);\n", state.a, state.a, state.b, state.b);
+ pathA.addRect(state.c, state.c, state.d, state.d, SkPath::kCW_Direction);
+ str += sprintf(str, " path.addRect(%d, %d, %d, %d,"
+ " SkPath::kCW_Direction);\n", state.c, state.c, state.d, state.d);
+ pathA.close();
+ pathB.setFillType((SkPath::FillType) f);
+ str += sprintf(str, " pathB.setFillType(SkPath::k%s_FillType);\n",
+ f == SkPath::kWinding_FillType ? "Winding" : f == SkPath::kEvenOdd_FillType
+ ? "EvenOdd" : "?UNDEFINED");
+ pathB.addRect(a, a, b, b, SkPath::kCW_Direction);
+ str += sprintf(str, " pathB.addRect(%d, %d, %d, %d,"
+ " SkPath::kCW_Direction);\n", a, a, b, b);
+ pathB.addRect(c, c, d, d, SkPath::kCW_Direction);
+ str += sprintf(str, " pathB.addRect(%d, %d, %d, %d,"
+ " SkPath::kCW_Direction);\n", c, c, d, d);
+ pathB.close();
+ outputProgress(state, pathStr, kDifference_Op);
+ testShapeOp(pathA, pathB, kDifference_Op);
+ state.testsRun++;
+ outputProgress(state, pathStr, kIntersect_Op);
+ testShapeOp(pathA, pathB, kIntersect_Op);
+ state.testsRun++;
+ outputProgress(state, pathStr, kUnion_Op);
+ testShapeOp(pathA, pathB, kUnion_Op);
+ state.testsRun++;
+ outputProgress(state, pathStr, kXor_Op);
+ testShapeOp(pathA, pathB, kXor_Op);
+ state.testsRun++;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ } while (runNextTestSet(state));
+ return NULL;
+}
+
+void ShapeOps4x4RectsThreaded_Test(int& testsRun)
+{
+ SkDebugf("%s\n", __FUNCTION__);
+#ifdef SK_DEBUG
+ gDebugMaxWindSum = 4;
+ gDebugMaxWindValue = 4;
+#endif
+ const char testLineStr[] = "testOp";
+ initializeTests(testLineStr, sizeof(testLineStr));
+ int testsStart = testsRun;
+ for (int a = 0; a < 6; ++a) { // outermost
+ for (int b = a + 1; b < 7; ++b) {
+ for (int c = 0 ; c < 6; ++c) {
+ for (int d = c + 1; d < 7; ++d) {
+ testsRun += dispatchTest4(testShapeOps4x4RectsMain, a, b, c, d);
+ }
+ if (!gRunTestsInOneThread) SkDebugf(".");
+ }
+ if (!gRunTestsInOneThread) SkDebugf("%d", b);
+ }
+ if (!gRunTestsInOneThread) SkDebugf("\n%d", a);
+ }
+ testsRun += waitForCompletion();
+ SkDebugf("%s tests=%d total=%d\n", __FUNCTION__, testsRun - testsStart, testsRun);
+}
diff --git a/experimental/Intersection/ShapeOps.cpp b/experimental/Intersection/ShapeOps.cpp
new file mode 100644
index 0000000000..281cf7f8ad
--- /dev/null
+++ b/experimental/Intersection/ShapeOps.cpp
@@ -0,0 +1,266 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "Simplify.h"
+
+namespace Op {
+
+#define INCLUDED_BY_SHAPE_OPS 1
+
+#include "Simplify.cpp"
+
+// FIXME: this and find chase should be merge together, along with
+// other code that walks winding in angles
+// OPTIMIZATION: Probably, the walked winding should be rolled into the angle structure
+// so it isn't duplicated by walkers like this one
+static Segment* findChaseOp(SkTDArray<Span*>& chase, int& nextStart, int& nextEnd) {
+ while (chase.count()) {
+ Span* span;
+ chase.pop(&span);
+ const Span& backPtr = span->fOther->span(span->fOtherIndex);
+ Segment* segment = backPtr.fOther;
+ nextStart = backPtr.fOtherIndex;
+ SkTDArray<Angle> angles;
+ int done = 0;
+ if (segment->activeAngle(nextStart, done, angles)) {
+ Angle* last = angles.end() - 1;
+ nextStart = last->start();
+ nextEnd = last->end();
+ #if TRY_ROTATE
+ *chase.insert(0) = span;
+ #else
+ *chase.append() = span;
+ #endif
+ return last->segment();
+ }
+ if (done == angles.count()) {
+ continue;
+ }
+ SkTDArray<Angle*> sorted;
+ bool sortable = Segment::SortAngles(angles, sorted);
+ int angleCount = sorted.count();
+#if DEBUG_SORT
+ sorted[0]->segment()->debugShowSort(__FUNCTION__, sorted, 0);
+#endif
+ if (!sortable) {
+ continue;
+ }
+ // find first angle, initialize winding to computed fWindSum
+ int firstIndex = -1;
+ const Angle* angle;
+ do {
+ angle = sorted[++firstIndex];
+ segment = angle->segment();
+ } while (segment->windSum(angle) == SK_MinS32);
+ #if DEBUG_SORT
+ segment->debugShowSort(__FUNCTION__, sorted, firstIndex);
+ #endif
+ int sumMiWinding = segment->updateWindingReverse(angle);
+ int sumSuWinding = segment->updateOppWindingReverse(angle);
+ if (segment->operand()) {
+ SkTSwap<int>(sumMiWinding, sumSuWinding);
+ }
+ int nextIndex = firstIndex + 1;
+ int lastIndex = firstIndex != 0 ? firstIndex : angleCount;
+ Segment* first = NULL;
+ do {
+ SkASSERT(nextIndex != firstIndex);
+ if (nextIndex == angleCount) {
+ nextIndex = 0;
+ }
+ int maxWinding, sumWinding, oppMaxWinding, oppSumWinding;
+ angle = sorted[nextIndex];
+ segment = angle->segment();
+ int start = angle->start();
+ int end = angle->end();
+ segment->setUpWindings(start, end, sumMiWinding, sumSuWinding,
+ maxWinding, sumWinding, oppMaxWinding, oppSumWinding);
+ if (!segment->done(angle)) {
+ if (!first) {
+ first = segment;
+ nextStart = start;
+ nextEnd = end;
+ }
+ (void) segment->markAngle(maxWinding, sumWinding, oppMaxWinding,
+ oppSumWinding, true, angle);
+ }
+ } while (++nextIndex != lastIndex);
+ if (first) {
+ #if TRY_ROTATE
+ *chase.insert(0) = span;
+ #else
+ *chase.append() = span;
+ #endif
+ return first;
+ }
+ }
+ return NULL;
+}
+
+/*
+static bool windingIsActive(int winding, int oppWinding, int spanWinding, int oppSpanWinding,
+ bool windingIsOp, ShapeOp op) {
+ bool active = windingIsActive(winding, spanWinding);
+ if (!active) {
+ return false;
+ }
+ if (oppSpanWinding && windingIsActive(oppWinding, oppSpanWinding)) {
+ switch (op) {
+ case kIntersect_Op:
+ case kUnion_Op:
+ return true;
+ case kDifference_Op: {
+ int absSpan = abs(spanWinding);
+ int absOpp = abs(oppSpanWinding);
+ return windingIsOp ? absSpan < absOpp : absSpan > absOpp;
+ }
+ case kXor_Op:
+ return spanWinding != oppSpanWinding;
+ default:
+ SkASSERT(0);
+ }
+ }
+ bool opActive = oppWinding != 0;
+ return gOpLookup[op][opActive][windingIsOp];
+}
+*/
+
+static bool bridgeOp(SkTDArray<Contour*>& contourList, const ShapeOp op,
+ const int xorMask, const int xorOpMask, PathWrapper& simple) {
+ bool firstContour = true;
+ bool unsortable = false;
+ bool topUnsortable = false;
+ bool firstRetry = false;
+ bool closable = true;
+ SkPoint topLeft = {SK_ScalarMin, SK_ScalarMin};
+ do {
+ int index, endIndex;
+ Segment* current = findSortableTopNew(contourList, firstContour, index, endIndex, topLeft,
+ topUnsortable);
+ if (!current) {
+ if (topUnsortable) {
+ topUnsortable = false;
+ SkASSERT(!firstRetry);
+ firstRetry = true;
+ topLeft.fX = topLeft.fY = SK_ScalarMin;
+ continue;
+ }
+ break;
+ }
+ SkTDArray<Span*> chaseArray;
+ do {
+ if (current->activeOp(index, endIndex, xorMask, xorOpMask, op)) {
+ bool active = true;
+ do {
+ #if DEBUG_ACTIVE_SPANS
+ if (!unsortable && current->done()) {
+ debugShowActiveSpans(contourList);
+ }
+ #endif
+ SkASSERT(unsortable || !current->done());
+ int nextStart = index;
+ int nextEnd = endIndex;
+ Segment* next = current->findNextOp(chaseArray, nextStart, nextEnd,
+ unsortable, op, xorMask, xorOpMask);
+ if (!next) {
+ SkASSERT(!unsortable);
+ if (!unsortable && simple.hasMove()
+ && current->verb() != SkPath::kLine_Verb
+ && !simple.isClosed()) {
+ current->addCurveTo(index, endIndex, simple, true);
+ SkASSERT(simple.isClosed());
+ }
+ active = false;
+ break;
+ }
+ current->addCurveTo(index, endIndex, simple, true);
+ current = next;
+ index = nextStart;
+ endIndex = nextEnd;
+ } while (!simple.isClosed() && ((!unsortable) || !current->done()));
+ if (active && !simple.isClosed()) {
+ SkASSERT(unsortable);
+ int min = SkMin32(index, endIndex);
+ if (!current->done(min)) {
+ current->addCurveTo(index, endIndex, simple, true);
+ current->markDoneBinary(min);
+ }
+ closable = false;
+ }
+ simple.close();
+ } else {
+ Span* last = current->markAndChaseDoneBinary(index, endIndex);
+ if (last) {
+ *chaseArray.append() = last;
+ }
+ }
+ current = findChaseOp(chaseArray, index, endIndex);
+ #if DEBUG_ACTIVE_SPANS
+ debugShowActiveSpans(contourList);
+ #endif
+ if (!current) {
+ break;
+ }
+ } while (true);
+ } while (true);
+ return closable;
+}
+
+} // end of Op namespace
+
+
+void operate(const SkPath& one, const SkPath& two, ShapeOp op, SkPath& result) {
+ result.reset();
+ result.setFillType(SkPath::kEvenOdd_FillType);
+ // turn path into list of segments
+ SkTArray<Op::Contour> contours;
+ // FIXME: add self-intersecting cubics' T values to segment
+ Op::EdgeBuilder builder(one, contours);
+ const int xorMask = builder.xorMask();
+ builder.addOperand(two);
+ builder.finish();
+ const int xorOpMask = builder.xorMask();
+ SkTDArray<Op::Contour*> contourList;
+ makeContourList(contours, contourList, xorMask == kEvenOdd_Mask,
+ xorOpMask == kEvenOdd_Mask);
+ Op::Contour** currentPtr = contourList.begin();
+ if (!currentPtr) {
+ return;
+ }
+ Op::Contour** listEnd = contourList.end();
+ // find all intersections between segments
+ do {
+ Op::Contour** nextPtr = currentPtr;
+ Op::Contour* current = *currentPtr++;
+ Op::Contour* next;
+ do {
+ next = *nextPtr++;
+ } while (addIntersectTs(current, next) && nextPtr != listEnd);
+ } while (currentPtr != listEnd);
+ // eat through coincident edges
+
+ int total = 0;
+ int index;
+ for (index = 0; index < contourList.count(); ++index) {
+ total += contourList[index]->segments().count();
+ }
+#if DEBUG_SHOW_WINDING
+ Op::Contour::debugShowWindingValues(contourList);
+#endif
+ coincidenceCheck(contourList, total);
+#if DEBUG_SHOW_WINDING
+ Op::Contour::debugShowWindingValues(contourList);
+#endif
+ fixOtherTIndex(contourList);
+ sortSegments(contourList);
+#if DEBUG_ACTIVE_SPANS
+ debugShowActiveSpans(contourList);
+#endif
+ // construct closed contours
+ Op::PathWrapper wrapper(result);
+ bridgeOp(contourList, op, xorMask, xorOpMask, wrapper);
+}
diff --git a/experimental/Intersection/ShapeOps.h b/experimental/Intersection/ShapeOps.h
new file mode 100644
index 0000000000..d6959b9ed1
--- /dev/null
+++ b/experimental/Intersection/ShapeOps.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SkPath.h"
+
+// region-inspired approach
+void contourBounds(const SkPath& path, SkTDArray<SkRect>& boundsArray);
+void simplify(const SkPath& path, bool asFill, SkPath& simple);
+
+// contour outer edge walking approach
+#ifndef DEFINE_SHAPE_OP
+// FIXME: namespace testing doesn't allow global enums like this
+#define DEFINE_SHAPE_OP
+enum ShapeOp {
+ kDifference_Op,
+ kIntersect_Op,
+ kUnion_Op,
+ kXor_Op,
+ kShapeOp_Count
+};
+
+enum ShapeOpMask {
+ kWinding_Mask = -1,
+ kNo_Mask = 0,
+ kEvenOdd_Mask = 1
+};
+#endif
+
+void operate(const SkPath& one, const SkPath& two, ShapeOp op, SkPath& result);
+void simplifyx(const SkPath& path, SkPath& simple);
+
+// FIXME: remove this section once debugging is complete
+extern const bool gRunTestsInOneThread;
+#ifdef SK_DEBUG
+extern int gDebugMaxWindSum;
+extern int gDebugMaxWindValue;
+#endif
diff --git a/experimental/Intersection/Simplify.cpp b/experimental/Intersection/Simplify.cpp
new file mode 100644
index 0000000000..6c6f5cb366
--- /dev/null
+++ b/experimental/Intersection/Simplify.cpp
@@ -0,0 +1,6268 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "Simplify.h"
+
+#undef SkASSERT
+#define SkASSERT(cond) while (!(cond)) { sk_throw(); }
+
+// Terminology:
+// A Path contains one of more Contours
+// A Contour is made up of Segment array
+// A Segment is described by a Verb and a Point array with 2, 3, or 4 points
+// A Verb is one of Line, Quad(ratic), or Cubic
+// A Segment contains a Span array
+// A Span is describes a portion of a Segment using starting and ending T
+// T values range from 0 to 1, where 0 is the first Point in the Segment
+// An Edge is a Segment generated from a Span
+
+// FIXME: remove once debugging is complete
+#ifdef SK_DEBUG
+int gDebugMaxWindSum = SK_MaxS32;
+int gDebugMaxWindValue = SK_MaxS32;
+#endif
+bool gUseOldBridgeWinding = false;
+
+#define PIN_ADD_T 0
+#define TRY_ROTATE 1
+
+#define DEBUG_UNUSED 0 // set to expose unused functions
+#define FORCE_RELEASE 0 // set force release to 1 for multiple thread -- no debugging
+
+#if FORCE_RELEASE || defined SK_RELEASE
+
+const bool gRunTestsInOneThread = false;
+
+#define DEBUG_ACTIVE_SPANS 0
+#define DEBUG_ACTIVE_SPANS_SHORT_FORM 0
+#define DEBUG_ADD_INTERSECTING_TS 0
+#define DEBUG_ADD_T_PAIR 0
+#define DEBUG_ANGLE 0
+#define DEBUG_ASSEMBLE 0
+#define DEBUG_CONCIDENT 0
+#define DEBUG_CROSS 0
+#define DEBUG_FLOW 0
+#define DEBUG_MARK_DONE 0
+#define DEBUG_PATH_CONSTRUCTION 0
+#define DEBUG_SHOW_WINDING 0
+#define DEBUG_SORT 0
+#define DEBUG_WIND_BUMP 0
+#define DEBUG_WINDING 0
+
+#else
+
+const bool gRunTestsInOneThread = true;
+
+#define DEBUG_ACTIVE_SPANS 1
+#define DEBUG_ACTIVE_SPANS_SHORT_FORM 1
+#define DEBUG_ADD_INTERSECTING_TS 1
+#define DEBUG_ADD_T_PAIR 1
+#define DEBUG_ANGLE 1
+#define DEBUG_ASSEMBLE 1
+#define DEBUG_CONCIDENT 1
+#define DEBUG_CROSS 0
+#define DEBUG_FLOW 1
+#define DEBUG_MARK_DONE 1
+#define DEBUG_PATH_CONSTRUCTION 1
+#define DEBUG_SHOW_WINDING 0
+#define DEBUG_SORT 1
+#define DEBUG_WIND_BUMP 0
+#define DEBUG_WINDING 1
+
+#endif
+
+#define DEBUG_DUMP (DEBUG_ACTIVE_SPANS | DEBUG_CONCIDENT | DEBUG_SORT | DEBUG_PATH_CONSTRUCTION)
+
+#if DEBUG_DUMP
+static const char* kLVerbStr[] = {"", "line", "quad", "cubic"};
+// static const char* kUVerbStr[] = {"", "Line", "Quad", "Cubic"};
+static int gContourID;
+static int gSegmentID;
+#endif
+
+#ifndef DEBUG_TEST
+#define DEBUG_TEST 0
+#endif
+
+#define MAKE_CONST_LINE(line, pts) \
+ const _Line line = {{pts[0].fX, pts[0].fY}, {pts[1].fX, pts[1].fY}}
+#define MAKE_CONST_QUAD(quad, pts) \
+ const Quadratic quad = {{pts[0].fX, pts[0].fY}, {pts[1].fX, pts[1].fY}, \
+ {pts[2].fX, pts[2].fY}}
+#define MAKE_CONST_CUBIC(cubic, pts) \
+ const Cubic cubic = {{pts[0].fX, pts[0].fY}, {pts[1].fX, pts[1].fY}, \
+ {pts[2].fX, pts[2].fY}, {pts[3].fX, pts[3].fY}}
+
+static int LineIntersect(const SkPoint a[2], const SkPoint b[2],
+ Intersections& intersections) {
+ MAKE_CONST_LINE(aLine, a);
+ MAKE_CONST_LINE(bLine, b);
+ return intersect(aLine, bLine, intersections.fT[0], intersections.fT[1]);
+}
+
+static int QuadLineIntersect(const SkPoint a[3], const SkPoint b[2],
+ Intersections& intersections) {
+ MAKE_CONST_QUAD(aQuad, a);
+ MAKE_CONST_LINE(bLine, b);
+ return intersect(aQuad, bLine, intersections);
+}
+
+static int CubicLineIntersect(const SkPoint a[4], const SkPoint b[2],
+ Intersections& intersections) {
+ MAKE_CONST_CUBIC(aCubic, a);
+ MAKE_CONST_LINE(bLine, b);
+ return intersect(aCubic, bLine, intersections.fT[0], intersections.fT[1]);
+}
+
+static int QuadIntersect(const SkPoint a[3], const SkPoint b[3],
+ Intersections& intersections) {
+ MAKE_CONST_QUAD(aQuad, a);
+ MAKE_CONST_QUAD(bQuad, b);
+#define TRY_QUARTIC_SOLUTION 1
+#if TRY_QUARTIC_SOLUTION
+ intersect2(aQuad, bQuad, intersections);
+#else
+ intersect(aQuad, bQuad, intersections);
+#endif
+ return intersections.fUsed ? intersections.fUsed : intersections.fCoincidentUsed;
+}
+
+static int CubicIntersect(const SkPoint a[4], const SkPoint b[4],
+ Intersections& intersections) {
+ MAKE_CONST_CUBIC(aCubic, a);
+ MAKE_CONST_CUBIC(bCubic, b);
+ intersect(aCubic, bCubic, intersections);
+ return intersections.fUsed;
+}
+
+static int HLineIntersect(const SkPoint a[2], SkScalar left, SkScalar right,
+ SkScalar y, bool flipped, Intersections& intersections) {
+ MAKE_CONST_LINE(aLine, a);
+ return horizontalIntersect(aLine, left, right, y, flipped, intersections);
+}
+
+static int HQuadIntersect(const SkPoint a[3], SkScalar left, SkScalar right,
+ SkScalar y, bool flipped, Intersections& intersections) {
+ MAKE_CONST_QUAD(aQuad, a);
+ return horizontalIntersect(aQuad, left, right, y, flipped, intersections);
+}
+
+static int HCubicIntersect(const SkPoint a[4], SkScalar left, SkScalar right,
+ SkScalar y, bool flipped, Intersections& intersections) {
+ MAKE_CONST_CUBIC(aCubic, a);
+ return horizontalIntersect(aCubic, left, right, y, flipped, intersections);
+}
+
+static int (* const HSegmentIntersect[])(const SkPoint [], SkScalar ,
+ SkScalar , SkScalar , bool , Intersections& ) = {
+ NULL,
+ HLineIntersect,
+ HQuadIntersect,
+ HCubicIntersect
+};
+
+static int VLineIntersect(const SkPoint a[2], SkScalar top, SkScalar bottom,
+ SkScalar x, bool flipped, Intersections& intersections) {
+ MAKE_CONST_LINE(aLine, a);
+ return verticalIntersect(aLine, top, bottom, x, flipped, intersections);
+}
+
+static int VQuadIntersect(const SkPoint a[3], SkScalar top, SkScalar bottom,
+ SkScalar x, bool flipped, Intersections& intersections) {
+ MAKE_CONST_QUAD(aQuad, a);
+ return verticalIntersect(aQuad, top, bottom, x, flipped, intersections);
+}
+
+static int VCubicIntersect(const SkPoint a[4], SkScalar top, SkScalar bottom,
+ SkScalar x, bool flipped, Intersections& intersections) {
+ MAKE_CONST_CUBIC(aCubic, a);
+ return verticalIntersect(aCubic, top, bottom, x, flipped, intersections);
+}
+
+static int (* const VSegmentIntersect[])(const SkPoint [], SkScalar ,
+ SkScalar , SkScalar , bool , Intersections& ) = {
+ NULL,
+ VLineIntersect,
+ VQuadIntersect,
+ VCubicIntersect
+};
+
+static void LineXYAtT(const SkPoint a[2], double t, SkPoint* out) {
+ MAKE_CONST_LINE(line, a);
+ double x, y;
+ xy_at_t(line, t, x, y);
+ out->fX = SkDoubleToScalar(x);
+ out->fY = SkDoubleToScalar(y);
+}
+
+static void QuadXYAtT(const SkPoint a[3], double t, SkPoint* out) {
+ MAKE_CONST_QUAD(quad, a);
+ double x, y;
+ xy_at_t(quad, t, x, y);
+ out->fX = SkDoubleToScalar(x);
+ out->fY = SkDoubleToScalar(y);
+}
+
+static void QuadXYAtT(const SkPoint a[3], double t, _Point* out) {
+ MAKE_CONST_QUAD(quad, a);
+ xy_at_t(quad, t, out->x, out->y);
+}
+
+static void CubicXYAtT(const SkPoint a[4], double t, SkPoint* out) {
+ MAKE_CONST_CUBIC(cubic, a);
+ double x, y;
+ xy_at_t(cubic, t, x, y);
+ out->fX = SkDoubleToScalar(x);
+ out->fY = SkDoubleToScalar(y);
+}
+
+static void (* const SegmentXYAtT[])(const SkPoint [], double , SkPoint* ) = {
+ NULL,
+ LineXYAtT,
+ QuadXYAtT,
+ CubicXYAtT
+};
+
+static SkScalar LineXAtT(const SkPoint a[2], double t) {
+ MAKE_CONST_LINE(aLine, a);
+ double x;
+ xy_at_t(aLine, t, x, *(double*) 0);
+ return SkDoubleToScalar(x);
+}
+
+static SkScalar QuadXAtT(const SkPoint a[3], double t) {
+ MAKE_CONST_QUAD(quad, a);
+ double x;
+ xy_at_t(quad, t, x, *(double*) 0);
+ return SkDoubleToScalar(x);
+}
+
+static SkScalar CubicXAtT(const SkPoint a[4], double t) {
+ MAKE_CONST_CUBIC(cubic, a);
+ double x;
+ xy_at_t(cubic, t, x, *(double*) 0);
+ return SkDoubleToScalar(x);
+}
+
+static SkScalar (* const SegmentXAtT[])(const SkPoint [], double ) = {
+ NULL,
+ LineXAtT,
+ QuadXAtT,
+ CubicXAtT
+};
+
+static SkScalar LineYAtT(const SkPoint a[2], double t) {
+ MAKE_CONST_LINE(aLine, a);
+ double y;
+ xy_at_t(aLine, t, *(double*) 0, y);
+ return SkDoubleToScalar(y);
+}
+
+static SkScalar QuadYAtT(const SkPoint a[3], double t) {
+ MAKE_CONST_QUAD(quad, a);
+ double y;
+ xy_at_t(quad, t, *(double*) 0, y);
+ return SkDoubleToScalar(y);
+}
+
+static SkScalar CubicYAtT(const SkPoint a[4], double t) {
+ MAKE_CONST_CUBIC(cubic, a);
+ double y;
+ xy_at_t(cubic, t, *(double*) 0, y);
+ return SkDoubleToScalar(y);
+}
+
+static SkScalar (* const SegmentYAtT[])(const SkPoint [], double ) = {
+ NULL,
+ LineYAtT,
+ QuadYAtT,
+ CubicYAtT
+};
+
+static SkScalar LineDXAtT(const SkPoint a[2], double ) {
+ return a[1].fX - a[0].fX;
+}
+
+static SkScalar QuadDXAtT(const SkPoint a[3], double t) {
+ MAKE_CONST_QUAD(quad, a);
+ double x;
+ dxdy_at_t(quad, t, x, *(double*) 0);
+ return SkDoubleToScalar(x);
+}
+
+static SkScalar CubicDXAtT(const SkPoint a[4], double t) {
+ MAKE_CONST_CUBIC(cubic, a);
+ double x;
+ dxdy_at_t(cubic, t, x, *(double*) 0);
+ return SkDoubleToScalar(x);
+}
+
+static SkScalar (* const SegmentDXAtT[])(const SkPoint [], double ) = {
+ NULL,
+ LineDXAtT,
+ QuadDXAtT,
+ CubicDXAtT
+};
+
+static SkScalar LineDYAtT(const SkPoint a[2], double ) {
+ return a[1].fY - a[0].fY;
+}
+
+static SkScalar QuadDYAtT(const SkPoint a[3], double t) {
+ MAKE_CONST_QUAD(quad, a);
+ double y;
+ dxdy_at_t(quad, t, *(double*) 0, y);
+ return SkDoubleToScalar(y);
+}
+
+static SkScalar CubicDYAtT(const SkPoint a[4], double t) {
+ MAKE_CONST_CUBIC(cubic, a);
+ double y;
+ dxdy_at_t(cubic, t, *(double*) 0, y);
+ return SkDoubleToScalar(y);
+}
+
+static SkScalar (* const SegmentDYAtT[])(const SkPoint [], double ) = {
+ NULL,
+ LineDYAtT,
+ QuadDYAtT,
+ CubicDYAtT
+};
+
+static void LineSubDivide(const SkPoint a[2], double startT, double endT,
+ SkPoint sub[2]) {
+ MAKE_CONST_LINE(aLine, a);
+ _Line dst;
+ sub_divide(aLine, startT, endT, dst);
+ sub[0].fX = SkDoubleToScalar(dst[0].x);
+ sub[0].fY = SkDoubleToScalar(dst[0].y);
+ sub[1].fX = SkDoubleToScalar(dst[1].x);
+ sub[1].fY = SkDoubleToScalar(dst[1].y);
+}
+
+static void QuadSubDivide(const SkPoint a[3], double startT, double endT,
+ SkPoint sub[3]) {
+ MAKE_CONST_QUAD(aQuad, a);
+ Quadratic dst;
+ sub_divide(aQuad, startT, endT, dst);
+ sub[0].fX = SkDoubleToScalar(dst[0].x);
+ sub[0].fY = SkDoubleToScalar(dst[0].y);
+ sub[1].fX = SkDoubleToScalar(dst[1].x);
+ sub[1].fY = SkDoubleToScalar(dst[1].y);
+ sub[2].fX = SkDoubleToScalar(dst[2].x);
+ sub[2].fY = SkDoubleToScalar(dst[2].y);
+}
+
+static void CubicSubDivide(const SkPoint a[4], double startT, double endT,
+ SkPoint sub[4]) {
+ MAKE_CONST_CUBIC(aCubic, a);
+ Cubic dst;
+ sub_divide(aCubic, startT, endT, dst);
+ sub[0].fX = SkDoubleToScalar(dst[0].x);
+ sub[0].fY = SkDoubleToScalar(dst[0].y);
+ sub[1].fX = SkDoubleToScalar(dst[1].x);
+ sub[1].fY = SkDoubleToScalar(dst[1].y);
+ sub[2].fX = SkDoubleToScalar(dst[2].x);
+ sub[2].fY = SkDoubleToScalar(dst[2].y);
+ sub[3].fX = SkDoubleToScalar(dst[3].x);
+ sub[3].fY = SkDoubleToScalar(dst[3].y);
+}
+
+static void (* const SegmentSubDivide[])(const SkPoint [], double , double ,
+ SkPoint []) = {
+ NULL,
+ LineSubDivide,
+ QuadSubDivide,
+ CubicSubDivide
+};
+
+static void LineSubDivideHD(const SkPoint a[2], double startT, double endT,
+ _Line sub) {
+ MAKE_CONST_LINE(aLine, a);
+ _Line dst;
+ sub_divide(aLine, startT, endT, dst);
+ sub[0] = dst[0];
+ sub[1] = dst[1];
+}
+
+static void QuadSubDivideHD(const SkPoint a[3], double startT, double endT,
+ Quadratic sub) {
+ MAKE_CONST_QUAD(aQuad, a);
+ Quadratic dst;
+ sub_divide(aQuad, startT, endT, dst);
+ sub[0] = dst[0];
+ sub[1] = dst[1];
+ sub[2] = dst[2];
+}
+
+static void CubicSubDivideHD(const SkPoint a[4], double startT, double endT,
+ Cubic sub) {
+ MAKE_CONST_CUBIC(aCubic, a);
+ Cubic dst;
+ sub_divide(aCubic, startT, endT, dst);
+ sub[0] = dst[0];
+ sub[1] = dst[1];
+ sub[2] = dst[2];
+ sub[3] = dst[3];
+}
+
+#if DEBUG_UNUSED
+static void QuadSubBounds(const SkPoint a[3], double startT, double endT,
+ SkRect& bounds) {
+ SkPoint dst[3];
+ QuadSubDivide(a, startT, endT, dst);
+ bounds.fLeft = bounds.fRight = dst[0].fX;
+ bounds.fTop = bounds.fBottom = dst[0].fY;
+ for (int index = 1; index < 3; ++index) {
+ bounds.growToInclude(dst[index].fX, dst[index].fY);
+ }
+}
+
+static void CubicSubBounds(const SkPoint a[4], double startT, double endT,
+ SkRect& bounds) {
+ SkPoint dst[4];
+ CubicSubDivide(a, startT, endT, dst);
+ bounds.fLeft = bounds.fRight = dst[0].fX;
+ bounds.fTop = bounds.fBottom = dst[0].fY;
+ for (int index = 1; index < 4; ++index) {
+ bounds.growToInclude(dst[index].fX, dst[index].fY);
+ }
+}
+#endif
+
+static SkPath::Verb QuadReduceOrder(const SkPoint a[3],
+ SkTDArray<SkPoint>& reducePts) {
+ MAKE_CONST_QUAD(aQuad, a);
+ Quadratic dst;
+ int order = reduceOrder(aQuad, dst);
+ if (order == 2) { // quad became line
+ for (int index = 0; index < order; ++index) {
+ SkPoint* pt = reducePts.append();
+ pt->fX = SkDoubleToScalar(dst[index].x);
+ pt->fY = SkDoubleToScalar(dst[index].y);
+ }
+ }
+ return (SkPath::Verb) (order - 1);
+}
+
+static SkPath::Verb CubicReduceOrder(const SkPoint a[4],
+ SkTDArray<SkPoint>& reducePts) {
+ MAKE_CONST_CUBIC(aCubic, a);
+ Cubic dst;
+ int order = reduceOrder(aCubic, dst, kReduceOrder_QuadraticsAllowed);
+ if (order == 2 || order == 3) { // cubic became line or quad
+ for (int index = 0; index < order; ++index) {
+ SkPoint* pt = reducePts.append();
+ pt->fX = SkDoubleToScalar(dst[index].x);
+ pt->fY = SkDoubleToScalar(dst[index].y);
+ }
+ }
+ return (SkPath::Verb) (order - 1);
+}
+
+static bool QuadIsLinear(const SkPoint a[3]) {
+ MAKE_CONST_QUAD(aQuad, a);
+ return isLinear(aQuad, 0, 2);
+}
+
+static bool CubicIsLinear(const SkPoint a[4]) {
+ MAKE_CONST_CUBIC(aCubic, a);
+ return isLinear(aCubic, 0, 3);
+}
+
+static SkScalar LineLeftMost(const SkPoint a[2], double startT, double endT) {
+ MAKE_CONST_LINE(aLine, a);
+ double x[2];
+ xy_at_t(aLine, startT, x[0], *(double*) 0);
+ xy_at_t(aLine, endT, x[1], *(double*) 0);
+ return SkMinScalar((float) x[0], (float) x[1]);
+}
+
+static SkScalar QuadLeftMost(const SkPoint a[3], double startT, double endT) {
+ MAKE_CONST_QUAD(aQuad, a);
+ return (float) leftMostT(aQuad, startT, endT);
+}
+
+static SkScalar CubicLeftMost(const SkPoint a[4], double startT, double endT) {
+ MAKE_CONST_CUBIC(aCubic, a);
+ return (float) leftMostT(aCubic, startT, endT);
+}
+
+static SkScalar (* const SegmentLeftMost[])(const SkPoint [], double , double) = {
+ NULL,
+ LineLeftMost,
+ QuadLeftMost,
+ CubicLeftMost
+};
+
+#if 0 // currently unused
+static int QuadRayIntersect(const SkPoint a[3], const SkPoint b[2],
+ Intersections& intersections) {
+ MAKE_CONST_QUAD(aQuad, a);
+ MAKE_CONST_LINE(bLine, b);
+ return intersectRay(aQuad, bLine, intersections);
+}
+#endif
+
+static int QuadRayIntersect(const SkPoint a[3], const _Line& bLine,
+ Intersections& intersections) {
+ MAKE_CONST_QUAD(aQuad, a);
+ return intersectRay(aQuad, bLine, intersections);
+}
+
+class Segment;
+
+struct Span {
+ Segment* fOther;
+ mutable SkPoint fPt; // lazily computed as needed
+ double fT;
+ double fOtherT; // value at fOther[fOtherIndex].fT
+ int fOtherIndex; // can't be used during intersection
+ int fWindSum; // accumulated from contours surrounding this one.
+ int fOppSum; // for binary operators: the opposite winding sum
+ int fWindValue; // 0 == canceled; 1 == normal; >1 == coincident
+ int fOppValue; // normally 0 -- when binary coincident edges combine, opp value goes here
+ bool fDone; // if set, this span to next higher T has been processed
+ bool fUnsortableStart; // set when start is part of an unsortable pair
+ bool fUnsortableEnd; // set when end is part of an unsortable pair
+ bool fTiny; // if set, span may still be considered once for edge following
+};
+
+// sorting angles
+// given angles of {dx dy ddx ddy dddx dddy} sort them
+class Angle {
+public:
+ // FIXME: this is bogus for quads and cubics
+ // if the quads and cubics' line from end pt to ctrl pt are coincident,
+ // there's no obvious way to determine the curve ordering from the
+ // derivatives alone. In particular, if one quadratic's coincident tangent
+ // is longer than the other curve, the final control point can place the
+ // longer curve on either side of the shorter one.
+ // Using Bezier curve focus http://cagd.cs.byu.edu/~tom/papers/bezclip.pdf
+ // may provide some help, but nothing has been figured out yet.
+
+ /*(
+ for quads and cubics, set up a parameterized line (e.g. LineParameters )
+ for points [0] to [1]. See if point [2] is on that line, or on one side
+ or the other. If it both quads' end points are on the same side, choose
+ the shorter tangent. If the tangents are equal, choose the better second
+ tangent angle
+
+ maybe I could set up LineParameters lazily
+ */
+ bool operator<(const Angle& rh) const {
+ double y = dy();
+ double ry = rh.dy();
+ if ((y < 0) ^ (ry < 0)) { // OPTIMIZATION: better to use y * ry < 0 ?
+ return y < 0;
+ }
+ double x = dx();
+ double rx = rh.dx();
+ if (y == 0 && ry == 0 && x * rx < 0) {
+ return x < rx;
+ }
+ double x_ry = x * ry;
+ double rx_y = rx * y;
+ double cmp = x_ry - rx_y;
+ if (!approximately_zero(cmp)) {
+ return cmp < 0;
+ }
+ if (approximately_zero(x_ry) && approximately_zero(rx_y)
+ && !approximately_zero_squared(cmp)) {
+ return cmp < 0;
+ }
+ // at this point, the initial tangent line is coincident
+ if (fSide * rh.fSide <= 0 && (!approximately_zero(fSide)
+ || !approximately_zero(rh.fSide))) {
+ // FIXME: running demo will trigger this assertion
+ // (don't know if commenting out will trigger further assertion or not)
+ // commenting it out allows demo to run in release, though
+ // SkASSERT(fSide != rh.fSide);
+ return fSide < rh.fSide;
+ }
+ // see if either curve can be lengthened and try the tangent compare again
+ if (cmp && (*fSpans)[fEnd].fOther != rh.fSegment // tangents not absolutely identical
+ && (*rh.fSpans)[rh.fEnd].fOther != fSegment) { // and not intersecting
+ Angle longer = *this;
+ Angle rhLonger = rh;
+ if (longer.lengthen() | rhLonger.lengthen()) {
+ return longer < rhLonger;
+ }
+ // what if we extend in the other direction?
+ longer = *this;
+ rhLonger = rh;
+ if (longer.reverseLengthen() | rhLonger.reverseLengthen()) {
+ return longer < rhLonger;
+ }
+ }
+ if ((fVerb == SkPath::kLine_Verb && approximately_zero(x) && approximately_zero(y))
+ || (rh.fVerb == SkPath::kLine_Verb
+ && approximately_zero(rx) && approximately_zero(ry))) {
+ // See general unsortable comment below. This case can happen when
+ // one line has a non-zero change in t but no change in x and y.
+ fUnsortable = true;
+ rh.fUnsortable = true;
+ return this < &rh; // even with no solution, return a stable sort
+ }
+ if ((*rh.fSpans)[SkMin32(rh.fStart, rh.fEnd)].fTiny
+ || (*fSpans)[SkMin32(fStart, fEnd)].fTiny) {
+ fUnsortable = true;
+ rh.fUnsortable = true;
+ return this < &rh; // even with no solution, return a stable sort
+ }
+ SkASSERT(fVerb == SkPath::kQuad_Verb); // worry about cubics later
+ SkASSERT(rh.fVerb == SkPath::kQuad_Verb);
+ // FIXME: until I can think of something better, project a ray from the
+ // end of the shorter tangent to midway between the end points
+ // through both curves and use the resulting angle to sort
+ // FIXME: some of this setup can be moved to set() if it works, or cached if it's expensive
+ double len = fTangent1.normalSquared();
+ double rlen = rh.fTangent1.normalSquared();
+ _Line ray;
+ Intersections i, ri;
+ int roots, rroots;
+ bool flip = false;
+ do {
+ const Quadratic& q = (len < rlen) ^ flip ? fQ : rh.fQ;
+ double midX = (q[0].x + q[2].x) / 2;
+ double midY = (q[0].y + q[2].y) / 2;
+ ray[0] = q[1];
+ ray[1].x = midX;
+ ray[1].y = midY;
+ SkASSERT(ray[0] != ray[1]);
+ roots = QuadRayIntersect(fPts, ray, i);
+ rroots = QuadRayIntersect(rh.fPts, ray, ri);
+ } while ((roots == 0 || rroots == 0) && (flip ^= true));
+ if (roots == 0 || rroots == 0) {
+ // FIXME: we don't have a solution in this case. The interim solution
+ // is to mark the edges as unsortable, exclude them from this and
+ // future computations, and allow the returned path to be fragmented
+ fUnsortable = true;
+ rh.fUnsortable = true;
+ return this < &rh; // even with no solution, return a stable sort
+ }
+ _Point loc;
+ double best = SK_ScalarInfinity;
+ double dx, dy, dist;
+ int index;
+ for (index = 0; index < roots; ++index) {
+ QuadXYAtT(fPts, i.fT[0][index], &loc);
+ dx = loc.x - ray[0].x;
+ dy = loc.y - ray[0].y;
+ dist = dx * dx + dy * dy;
+ if (best > dist) {
+ best = dist;
+ }
+ }
+ for (index = 0; index < rroots; ++index) {
+ QuadXYAtT(rh.fPts, ri.fT[0][index], &loc);
+ dx = loc.x - ray[0].x;
+ dy = loc.y - ray[0].y;
+ dist = dx * dx + dy * dy;
+ if (best > dist) {
+ return fSide < 0;
+ }
+ }
+ return fSide > 0;
+ }
+
+ double dx() const {
+ return fTangent1.dx();
+ }
+
+ double dy() const {
+ return fTangent1.dy();
+ }
+
+ int end() const {
+ return fEnd;
+ }
+
+ bool isHorizontal() const {
+ return dy() == 0 && fVerb == SkPath::kLine_Verb;
+ }
+
+ bool lengthen() {
+ int newEnd = fEnd;
+ if (fStart < fEnd ? ++newEnd < fSpans->count() : --newEnd >= 0) {
+ fEnd = newEnd;
+ setSpans();
+ return true;
+ }
+ return false;
+ }
+
+ bool reverseLengthen() {
+ if (fReversed) {
+ return false;
+ }
+ int newEnd = fStart;
+ if (fStart > fEnd ? ++newEnd < fSpans->count() : --newEnd >= 0) {
+ fEnd = newEnd;
+ fReversed = true;
+ setSpans();
+ return true;
+ }
+ return false;
+ }
+
+ void set(const SkPoint* orig, SkPath::Verb verb, const Segment* segment,
+ int start, int end, const SkTDArray<Span>& spans) {
+ fSegment = segment;
+ fStart = start;
+ fEnd = end;
+ fPts = orig;
+ fVerb = verb;
+ fSpans = &spans;
+ fReversed = false;
+ fUnsortable = false;
+ setSpans();
+ }
+
+ void setSpans() {
+ double startT = (*fSpans)[fStart].fT;
+ double endT = (*fSpans)[fEnd].fT;
+ switch (fVerb) {
+ case SkPath::kLine_Verb:
+ _Line l;
+ LineSubDivideHD(fPts, startT, endT, l);
+ // OPTIMIZATION: for pure line compares, we never need fTangent1.c
+ fTangent1.lineEndPoints(l);
+ fUnsortable = dx() == 0 && dy() == 0;
+ fSide = 0;
+ break;
+ case SkPath::kQuad_Verb:
+ QuadSubDivideHD(fPts, startT, endT, fQ);
+ fTangent1.quadEndPoints(fQ, 0, 1);
+ fSide = -fTangent1.pointDistance(fQ[2]); // not normalized -- compare sign only
+ break;
+ case SkPath::kCubic_Verb:
+ Cubic c;
+ CubicSubDivideHD(fPts, startT, endT, c);
+ fTangent1.cubicEndPoints(c, 0, 1);
+ fSide = -fTangent1.pointDistance(c[2]); // not normalized -- compare sign only
+ break;
+ default:
+ SkASSERT(0);
+ }
+ if (fUnsortable) {
+ return;
+ }
+ SkASSERT(fStart != fEnd);
+ int step = fStart < fEnd ? 1 : -1; // OPTIMIZE: worth fStart - fEnd >> 31 type macro?
+ for (int index = fStart; index != fEnd; index += step) {
+ if ((*fSpans)[index].fUnsortableStart) {
+ fUnsortable = true;
+ return;
+ }
+#if 0
+ if (index != fStart && (*fSpans)[index].fUnsortableEnd) {
+ SkASSERT(0);
+ fUnsortable = true;
+ return;
+ }
+#endif
+ }
+ }
+
+ Segment* segment() const {
+ return const_cast<Segment*>(fSegment);
+ }
+
+ int sign() const {
+ return SkSign32(fStart - fEnd);
+ }
+
+ const SkTDArray<Span>* spans() const {
+ return fSpans;
+ }
+
+ int start() const {
+ return fStart;
+ }
+
+ bool unsortable() const {
+ return fUnsortable;
+ }
+
+#if DEBUG_ANGLE
+ const SkPoint* pts() const {
+ return fPts;
+ }
+
+ SkPath::Verb verb() const {
+ return fVerb;
+ }
+
+ void debugShow(const SkPoint& a) const {
+ SkDebugf(" d=(%1.9g,%1.9g) side=%1.9g\n", dx(), dy(), fSide);
+ }
+#endif
+
+private:
+ const SkPoint* fPts;
+ Quadratic fQ;
+ SkPath::Verb fVerb;
+ double fSide;
+ LineParameters fTangent1;
+ const SkTDArray<Span>* fSpans;
+ const Segment* fSegment;
+ int fStart;
+ int fEnd;
+ bool fReversed;
+ mutable bool fUnsortable; // this alone is editable by the less than operator
+};
+
+// Bounds, unlike Rect, does not consider a line to be empty.
+struct Bounds : public SkRect {
+ static bool Intersects(const Bounds& a, const Bounds& b) {
+ return a.fLeft <= b.fRight && b.fLeft <= a.fRight &&
+ a.fTop <= b.fBottom && b.fTop <= a.fBottom;
+ }
+
+ void add(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom) {
+ if (left < fLeft) {
+ fLeft = left;
+ }
+ if (top < fTop) {
+ fTop = top;
+ }
+ if (right > fRight) {
+ fRight = right;
+ }
+ if (bottom > fBottom) {
+ fBottom = bottom;
+ }
+ }
+
+ void add(const Bounds& toAdd) {
+ add(toAdd.fLeft, toAdd.fTop, toAdd.fRight, toAdd.fBottom);
+ }
+
+ void add(const SkPoint& pt) {
+ if (pt.fX < fLeft) fLeft = pt.fX;
+ if (pt.fY < fTop) fTop = pt.fY;
+ if (pt.fX > fRight) fRight = pt.fX;
+ if (pt.fY > fBottom) fBottom = pt.fY;
+ }
+
+ bool isEmpty() {
+ return fLeft > fRight || fTop > fBottom
+ || (fLeft == fRight && fTop == fBottom)
+ || isnan(fLeft) || isnan(fRight)
+ || isnan(fTop) || isnan(fBottom);
+ }
+
+ void setCubicBounds(const SkPoint a[4]) {
+ _Rect dRect;
+ MAKE_CONST_CUBIC(cubic, a);
+ dRect.setBounds(cubic);
+ set((float) dRect.left, (float) dRect.top, (float) dRect.right,
+ (float) dRect.bottom);
+ }
+
+ void setQuadBounds(const SkPoint a[3]) {
+ MAKE_CONST_QUAD(quad, a);
+ _Rect dRect;
+ dRect.setBounds(quad);
+ set((float) dRect.left, (float) dRect.top, (float) dRect.right,
+ (float) dRect.bottom);
+ }
+
+ void setPoint(const SkPoint& pt) {
+ fLeft = fRight = pt.fX;
+ fTop = fBottom = pt.fY;
+ }
+};
+
+// OPTIMIZATION: does the following also work, and is it any faster?
+// return outerWinding * innerWinding > 0
+// || ((outerWinding + innerWinding < 0) ^ ((outerWinding - innerWinding) < 0)))
+static bool useInnerWinding(int outerWinding, int innerWinding) {
+ // SkASSERT(outerWinding != innerWinding);
+ int absOut = abs(outerWinding);
+ int absIn = abs(innerWinding);
+ bool result = absOut == absIn ? outerWinding < 0 : absOut < absIn;
+ if (outerWinding * innerWinding < 0) {
+#if DEBUG_WINDING
+ SkDebugf("%s outer=%d inner=%d result=%s\n", __FUNCTION__,
+ outerWinding, innerWinding, result ? "true" : "false");
+#endif
+ }
+ return result;
+}
+
+#define F (false) // discard the edge
+#define T (true) // keep the edge
+
+static const bool gUnaryActiveEdge[2][2] = {
+// from=0 from=1
+// to=0,1 to=0,1
+ {F, T}, {T, F},
+};
+
+static const bool gActiveEdge[kShapeOp_Count][2][2][2][2] = {
+// miFrom=0 miFrom=1
+// miTo=0 miTo=1 miTo=0 miTo=1
+// suFrom=0 1 suFrom=0 1 suFrom=0 1 suFrom=0 1
+// suTo=0,1 suTo=0,1 suTo=0,1 suTo=0,1 suTo=0,1 suTo=0,1 suTo=0,1 suTo=0,1
+ {{{{F, F}, {F, F}}, {{T, F}, {T, F}}}, {{{T, T}, {F, F}}, {{F, T}, {T, F}}}}, // mi - su
+ {{{{F, F}, {F, F}}, {{F, T}, {F, T}}}, {{{F, F}, {T, T}}, {{F, T}, {T, F}}}}, // mi & su
+ {{{{F, T}, {T, F}}, {{T, T}, {F, F}}}, {{{T, F}, {T, F}}, {{F, F}, {F, F}}}}, // mi | su
+ {{{{F, T}, {T, F}}, {{T, F}, {F, T}}}, {{{T, F}, {F, T}}, {{F, T}, {T, F}}}}, // mi ^ su
+};
+
+#undef F
+#undef T
+
+// wrap path to keep track of whether the contour is initialized and non-empty
+class PathWrapper {
+public:
+ PathWrapper(SkPath& path)
+ : fPathPtr(&path)
+ , fCloses(0)
+ , fMoves(0)
+ {
+ init();
+ }
+
+ void close() {
+ if (!fHasMove) {
+ return;
+ }
+ bool callClose = isClosed();
+ lineTo();
+ if (fEmpty) {
+ return;
+ }
+ if (callClose) {
+ #if DEBUG_PATH_CONSTRUCTION
+ SkDebugf("path.close();\n");
+ #endif
+ fPathPtr->close();
+ fCloses++;
+ }
+ init();
+ }
+
+ void cubicTo(const SkPoint& pt1, const SkPoint& pt2, const SkPoint& pt3) {
+ lineTo();
+ moveTo();
+#if DEBUG_PATH_CONSTRUCTION
+ SkDebugf("path.cubicTo(%1.9g,%1.9g, %1.9g,%1.9g, %1.9g,%1.9g);\n",
+ pt1.fX, pt1.fY, pt2.fX, pt2.fY, pt3.fX, pt3.fY);
+#endif
+ fPathPtr->cubicTo(pt1.fX, pt1.fY, pt2.fX, pt2.fY, pt3.fX, pt3.fY);
+ fDefer[0] = fDefer[1] = pt3;
+ fEmpty = false;
+ }
+
+ void deferredLine(const SkPoint& pt) {
+ if (pt == fDefer[1]) {
+ return;
+ }
+ if (changedSlopes(pt)) {
+ lineTo();
+ fDefer[0] = fDefer[1];
+ }
+ fDefer[1] = pt;
+ }
+
+ void deferredMove(const SkPoint& pt) {
+ fMoved = true;
+ fHasMove = true;
+ fEmpty = true;
+ fDefer[0] = fDefer[1] = pt;
+ }
+
+ void deferredMoveLine(const SkPoint& pt) {
+ if (!fHasMove) {
+ deferredMove(pt);
+ }
+ deferredLine(pt);
+ }
+
+ bool hasMove() const {
+ return fHasMove;
+ }
+
+ void init() {
+ fEmpty = true;
+ fHasMove = false;
+ fMoved = false;
+ }
+
+ bool isClosed() const {
+ return !fEmpty && fFirstPt == fDefer[1];
+ }
+
+ void lineTo() {
+ if (fDefer[0] == fDefer[1]) {
+ return;
+ }
+ moveTo();
+ fEmpty = false;
+#if DEBUG_PATH_CONSTRUCTION
+ SkDebugf("path.lineTo(%1.9g,%1.9g);\n", fDefer[1].fX, fDefer[1].fY);
+#endif
+ fPathPtr->lineTo(fDefer[1].fX, fDefer[1].fY);
+ fDefer[0] = fDefer[1];
+ }
+
+ const SkPath* nativePath() const {
+ return fPathPtr;
+ }
+
+ void quadTo(const SkPoint& pt1, const SkPoint& pt2) {
+ lineTo();
+ moveTo();
+#if DEBUG_PATH_CONSTRUCTION
+ SkDebugf("path.quadTo(%1.9g,%1.9g, %1.9g,%1.9g);\n",
+ pt1.fX, pt1.fY, pt2.fX, pt2.fY);
+#endif
+ fPathPtr->quadTo(pt1.fX, pt1.fY, pt2.fX, pt2.fY);
+ fDefer[0] = fDefer[1] = pt2;
+ fEmpty = false;
+ }
+
+ bool someAssemblyRequired() const {
+ return fCloses < fMoves;
+ }
+
+protected:
+ bool changedSlopes(const SkPoint& pt) const {
+ if (fDefer[0] == fDefer[1]) {
+ return false;
+ }
+ SkScalar deferDx = fDefer[1].fX - fDefer[0].fX;
+ SkScalar deferDy = fDefer[1].fY - fDefer[0].fY;
+ SkScalar lineDx = pt.fX - fDefer[1].fX;
+ SkScalar lineDy = pt.fY - fDefer[1].fY;
+ return deferDx * lineDy != deferDy * lineDx;
+ }
+
+ void moveTo() {
+ if (!fMoved) {
+ return;
+ }
+ fFirstPt = fDefer[0];
+#if DEBUG_PATH_CONSTRUCTION
+ SkDebugf("path.moveTo(%1.9g,%1.9g);\n", fDefer[0].fX, fDefer[0].fY);
+#endif
+ fPathPtr->moveTo(fDefer[0].fX, fDefer[0].fY);
+ fMoved = false;
+ fMoves++;
+ }
+
+private:
+ SkPath* fPathPtr;
+ SkPoint fDefer[2];
+ SkPoint fFirstPt;
+ int fCloses;
+ int fMoves;
+ bool fEmpty;
+ bool fHasMove;
+ bool fMoved;
+};
+
+class Segment {
+public:
+ Segment() {
+#if DEBUG_DUMP
+ fID = ++gSegmentID;
+#endif
+ }
+
+ bool operator<(const Segment& rh) const {
+ return fBounds.fTop < rh.fBounds.fTop;
+ }
+
+ bool activeAngle(int index, int& done, SkTDArray<Angle>& angles) {
+ if (activeAngleInner(index, done, angles)) {
+ return true;
+ }
+ double referenceT = fTs[index].fT;
+ int lesser = index;
+ while (--lesser >= 0 && approximately_negative(referenceT - fTs[lesser].fT)) {
+ if (activeAngleOther(lesser, done, angles)) {
+ return true;
+ }
+ }
+ do {
+ if (activeAngleOther(index, done, angles)) {
+ return true;
+ }
+ } while (++index < fTs.count() && approximately_negative(fTs[index].fT - referenceT));
+ return false;
+ }
+
+ bool activeAngleOther(int index, int& done, SkTDArray<Angle>& angles) {
+ Span* span = &fTs[index];
+ Segment* other = span->fOther;
+ int oIndex = span->fOtherIndex;
+ return other->activeAngleInner(oIndex, done, angles);
+ }
+
+ bool activeAngleInner(int index, int& done, SkTDArray<Angle>& angles) {
+ int next = nextExactSpan(index, 1);
+ if (next > 0) {
+ Span& upSpan = fTs[index];
+ if (upSpan.fWindValue || upSpan.fOppValue) {
+ addAngle(angles, index, next);
+ if (upSpan.fDone || upSpan.fUnsortableEnd) {
+ done++;
+ } else if (upSpan.fWindSum != SK_MinS32) {
+ return true;
+ }
+ } else if (!upSpan.fDone) {
+ upSpan.fDone = true;
+ fDoneSpans++;
+ }
+ }
+ int prev = nextExactSpan(index, -1);
+ // edge leading into junction
+ if (prev >= 0) {
+ Span& downSpan = fTs[prev];
+ if (downSpan.fWindValue || downSpan.fOppValue) {
+ addAngle(angles, index, prev);
+ if (downSpan.fDone) {
+ done++;
+ } else if (downSpan.fWindSum != SK_MinS32) {
+ return true;
+ }
+ } else if (!downSpan.fDone) {
+ downSpan.fDone = true;
+ fDoneSpans++;
+ }
+ }
+ return false;
+ }
+
+ void activeLeftTop(SkPoint& result) const {
+ SkASSERT(!done());
+ int count = fTs.count();
+ result.fY = SK_ScalarMax;
+ bool lastDone = true;
+ bool lastUnsortable = false;
+ for (int index = 0; index < count; ++index) {
+ const Span& span = fTs[index];
+ if (span.fUnsortableStart | lastUnsortable) {
+ goto next;
+ }
+ if (!span.fDone | !lastDone) {
+ const SkPoint& xy = xyAtT(index);
+ if (result.fY < xy.fY) {
+ goto next;
+ }
+ if (result.fY == xy.fY && result.fX < xy.fX) {
+ goto next;
+ }
+ result = xy;
+ }
+ next:
+ lastDone = span.fDone;
+ lastUnsortable = span.fUnsortableEnd;
+ }
+ SkASSERT(result.fY < SK_ScalarMax);
+ }
+
+ bool activeOp(int index, int endIndex, int xorMiMask, int xorSuMask, ShapeOp op) {
+ int sumMiWinding = updateWinding(endIndex, index);
+ int sumSuWinding = updateOppWinding(endIndex, index);
+ if (fOperand) {
+ SkTSwap<int>(sumMiWinding, sumSuWinding);
+ }
+ int maxWinding, sumWinding, oppMaxWinding, oppSumWinding;
+ return activeOp(xorMiMask, xorSuMask, index, endIndex, op, sumMiWinding, sumSuWinding,
+ maxWinding, sumWinding, oppMaxWinding, oppSumWinding);
+ }
+
+ bool activeOp(int xorMiMask, int xorSuMask, int index, int endIndex, ShapeOp op,
+ int& sumMiWinding, int& sumSuWinding,
+ int& maxWinding, int& sumWinding, int& oppMaxWinding, int& oppSumWinding) {
+ setUpWindings(index, endIndex, sumMiWinding, sumSuWinding,
+ maxWinding, sumWinding, oppMaxWinding, oppSumWinding);
+ bool miFrom;
+ bool miTo;
+ bool suFrom;
+ bool suTo;
+ if (operand()) {
+ miFrom = (oppMaxWinding & xorMiMask) != 0;
+ miTo = (oppSumWinding & xorMiMask) != 0;
+ suFrom = (maxWinding & xorSuMask) != 0;
+ suTo = (sumWinding & xorSuMask) != 0;
+ } else {
+ miFrom = (maxWinding & xorMiMask) != 0;
+ miTo = (sumWinding & xorMiMask) != 0;
+ suFrom = (oppMaxWinding & xorSuMask) != 0;
+ suTo = (oppSumWinding & xorSuMask) != 0;
+ }
+ bool result = gActiveEdge[op][miFrom][miTo][suFrom][suTo];
+ SkASSERT(result != -1);
+ return result;
+ }
+
+ bool activeWinding(int index, int endIndex) {
+ int sumWinding = updateWinding(endIndex, index);
+ int maxWinding;
+ return activeWinding(index, endIndex, maxWinding, sumWinding);
+ }
+
+ bool activeWinding(int index, int endIndex, int& maxWinding, int& sumWinding) {
+ setUpWinding(index, endIndex, maxWinding, sumWinding);
+ bool from = maxWinding != 0;
+ bool to = sumWinding != 0;
+ bool result = gUnaryActiveEdge[from][to];
+ SkASSERT(result != -1);
+ return result;
+ }
+
+ void addAngle(SkTDArray<Angle>& angles, int start, int end) const {
+ SkASSERT(start != end);
+ Angle* angle = angles.append();
+#if DEBUG_ANGLE
+ if (angles.count() > 1 && !fTs[start].fTiny) {
+ SkPoint angle0Pt, newPt;
+ (*SegmentXYAtT[angles[0].verb()])(angles[0].pts(),
+ (*angles[0].spans())[angles[0].start()].fT, &angle0Pt);
+ (*SegmentXYAtT[fVerb])(fPts, fTs[start].fT, &newPt);
+ SkASSERT(approximately_equal(angle0Pt.fX, newPt.fX));
+ SkASSERT(approximately_equal(angle0Pt.fY, newPt.fY));
+ }
+#endif
+ angle->set(fPts, fVerb, this, start, end, fTs);
+ }
+
+ void addCancelOutsides(double tStart, double oStart, Segment& other,
+ double oEnd) {
+ int tIndex = -1;
+ int tCount = fTs.count();
+ int oIndex = -1;
+ int oCount = other.fTs.count();
+ do {
+ ++tIndex;
+ } while (!approximately_negative(tStart - fTs[tIndex].fT) && tIndex < tCount);
+ int tIndexStart = tIndex;
+ do {
+ ++oIndex;
+ } while (!approximately_negative(oStart - other.fTs[oIndex].fT) && oIndex < oCount);
+ int oIndexStart = oIndex;
+ double nextT;
+ do {
+ nextT = fTs[++tIndex].fT;
+ } while (nextT < 1 && approximately_negative(nextT - tStart));
+ double oNextT;
+ do {
+ oNextT = other.fTs[++oIndex].fT;
+ } while (oNextT < 1 && approximately_negative(oNextT - oStart));
+ // at this point, spans before and after are at:
+ // fTs[tIndexStart - 1], fTs[tIndexStart], fTs[tIndex]
+ // if tIndexStart == 0, no prior span
+ // if nextT == 1, no following span
+
+ // advance the span with zero winding
+ // if the following span exists (not past the end, non-zero winding)
+ // connect the two edges
+ if (!fTs[tIndexStart].fWindValue) {
+ if (tIndexStart > 0 && fTs[tIndexStart - 1].fWindValue) {
+ #if DEBUG_CONCIDENT
+ SkDebugf("%s 1 this=%d other=%d t [%d] %1.9g (%1.9g,%1.9g)\n",
+ __FUNCTION__, fID, other.fID, tIndexStart - 1,
+ fTs[tIndexStart].fT, xyAtT(tIndexStart).fX,
+ xyAtT(tIndexStart).fY);
+ #endif
+ addTPair(fTs[tIndexStart].fT, other, other.fTs[oIndex].fT, false);
+ }
+ if (nextT < 1 && fTs[tIndex].fWindValue) {
+ #if DEBUG_CONCIDENT
+ SkDebugf("%s 2 this=%d other=%d t [%d] %1.9g (%1.9g,%1.9g)\n",
+ __FUNCTION__, fID, other.fID, tIndex,
+ fTs[tIndex].fT, xyAtT(tIndex).fX,
+ xyAtT(tIndex).fY);
+ #endif
+ addTPair(fTs[tIndex].fT, other, other.fTs[oIndexStart].fT, false);
+ }
+ } else {
+ SkASSERT(!other.fTs[oIndexStart].fWindValue);
+ if (oIndexStart > 0 && other.fTs[oIndexStart - 1].fWindValue) {
+ #if DEBUG_CONCIDENT
+ SkDebugf("%s 3 this=%d other=%d t [%d] %1.9g (%1.9g,%1.9g)\n",
+ __FUNCTION__, fID, other.fID, oIndexStart - 1,
+ other.fTs[oIndexStart].fT, other.xyAtT(oIndexStart).fX,
+ other.xyAtT(oIndexStart).fY);
+ other.debugAddTPair(other.fTs[oIndexStart].fT, *this, fTs[tIndex].fT);
+ #endif
+ }
+ if (oNextT < 1 && other.fTs[oIndex].fWindValue) {
+ #if DEBUG_CONCIDENT
+ SkDebugf("%s 4 this=%d other=%d t [%d] %1.9g (%1.9g,%1.9g)\n",
+ __FUNCTION__, fID, other.fID, oIndex,
+ other.fTs[oIndex].fT, other.xyAtT(oIndex).fX,
+ other.xyAtT(oIndex).fY);
+ other.debugAddTPair(other.fTs[oIndex].fT, *this, fTs[tIndexStart].fT);
+ #endif
+ }
+ }
+ }
+
+ void addCoinOutsides(const SkTDArray<double>& outsideTs, Segment& other,
+ double oEnd) {
+ // walk this to outsideTs[0]
+ // walk other to outsideTs[1]
+ // if either is > 0, add a pointer to the other, copying adjacent winding
+ int tIndex = -1;
+ int oIndex = -1;
+ double tStart = outsideTs[0];
+ double oStart = outsideTs[1];
+ do {
+ ++tIndex;
+ } while (!approximately_negative(tStart - fTs[tIndex].fT));
+ do {
+ ++oIndex;
+ } while (!approximately_negative(oStart - other.fTs[oIndex].fT));
+ if (tIndex > 0 || oIndex > 0 || fOperand != other.fOperand) {
+ addTPair(tStart, other, oStart, false);
+ }
+ tStart = fTs[tIndex].fT;
+ oStart = other.fTs[oIndex].fT;
+ do {
+ double nextT;
+ do {
+ nextT = fTs[++tIndex].fT;
+ } while (approximately_negative(nextT - tStart));
+ tStart = nextT;
+ do {
+ nextT = other.fTs[++oIndex].fT;
+ } while (approximately_negative(nextT - oStart));
+ oStart = nextT;
+ if (tStart == 1 && oStart == 1 && fOperand == other.fOperand) {
+ break;
+ }
+ addTPair(tStart, other, oStart, false);
+ } while (tStart < 1 && oStart < 1 && !approximately_negative(oEnd - oStart));
+ }
+
+ void addCubic(const SkPoint pts[4], bool operand, bool evenOdd) {
+ init(pts, SkPath::kCubic_Verb, operand, evenOdd);
+ fBounds.setCubicBounds(pts);
+ }
+
+ /* SkPoint */ void addCurveTo(int start, int end, PathWrapper& path, bool active) const {
+ SkPoint edge[4];
+ const SkPoint* ePtr;
+ int lastT = fTs.count() - 1;
+ if (lastT < 0 || (start == 0 && end == lastT) || (start == lastT && end == 0)) {
+ ePtr = fPts;
+ } else {
+ // OPTIMIZE? if not active, skip remainder and return xy_at_t(end)
+ (*SegmentSubDivide[fVerb])(fPts, fTs[start].fT, fTs[end].fT, edge);
+ ePtr = edge;
+ }
+ if (active) {
+ bool reverse = ePtr == fPts && start != 0;
+ if (reverse) {
+ path.deferredMoveLine(ePtr[fVerb]);
+ switch (fVerb) {
+ case SkPath::kLine_Verb:
+ path.deferredLine(ePtr[0]);
+ break;
+ case SkPath::kQuad_Verb:
+ path.quadTo(ePtr[1], ePtr[0]);
+ break;
+ case SkPath::kCubic_Verb:
+ path.cubicTo(ePtr[2], ePtr[1], ePtr[0]);
+ break;
+ default:
+ SkASSERT(0);
+ }
+ // return ePtr[0];
+ } else {
+ path.deferredMoveLine(ePtr[0]);
+ switch (fVerb) {
+ case SkPath::kLine_Verb:
+ path.deferredLine(ePtr[1]);
+ break;
+ case SkPath::kQuad_Verb:
+ path.quadTo(ePtr[1], ePtr[2]);
+ break;
+ case SkPath::kCubic_Verb:
+ path.cubicTo(ePtr[1], ePtr[2], ePtr[3]);
+ break;
+ default:
+ SkASSERT(0);
+ }
+ }
+ }
+ // return ePtr[fVerb];
+ }
+
+ void addLine(const SkPoint pts[2], bool operand, bool evenOdd) {
+ init(pts, SkPath::kLine_Verb, operand, evenOdd);
+ fBounds.set(pts, 2);
+ }
+
+#if 0
+ const SkPoint& addMoveTo(int tIndex, PathWrapper& path, bool active) const {
+ const SkPoint& pt = xyAtT(tIndex);
+ if (active) {
+ path.deferredMove(pt);
+ }
+ return pt;
+ }
+#endif
+
+ // add 2 to edge or out of range values to get T extremes
+ void addOtherT(int index, double otherT, int otherIndex) {
+ Span& span = fTs[index];
+ #if PIN_ADD_T
+ if (precisely_less_than_zero(otherT)) {
+ otherT = 0;
+ } else if (precisely_greater_than_one(otherT)) {
+ otherT = 1;
+ }
+ #endif
+ span.fOtherT = otherT;
+ span.fOtherIndex = otherIndex;
+ }
+
+ void addQuad(const SkPoint pts[3], bool operand, bool evenOdd) {
+ init(pts, SkPath::kQuad_Verb, operand, evenOdd);
+ fBounds.setQuadBounds(pts);
+ }
+
+ // Defer all coincident edge processing until
+ // after normal intersections have been computed
+
+// no need to be tricky; insert in normal T order
+// resolve overlapping ts when considering coincidence later
+
+ // add non-coincident intersection. Resulting edges are sorted in T.
+ int addT(double newT, Segment* other) {
+ // FIXME: in the pathological case where there is a ton of intercepts,
+ // binary search?
+ int insertedAt = -1;
+ size_t tCount = fTs.count();
+ #if PIN_ADD_T
+ // FIXME: only do this pinning here (e.g. this is done also in quad/line intersect)
+ if (precisely_less_than_zero(newT)) {
+ newT = 0;
+ } else if (precisely_greater_than_one(newT)) {
+ newT = 1;
+ }
+ #endif
+ for (size_t index = 0; index < tCount; ++index) {
+ // OPTIMIZATION: if there are three or more identical Ts, then
+ // the fourth and following could be further insertion-sorted so
+ // that all the edges are clockwise or counterclockwise.
+ // This could later limit segment tests to the two adjacent
+ // neighbors, although it doesn't help with determining which
+ // circular direction to go in.
+ if (newT < fTs[index].fT) {
+ insertedAt = index;
+ break;
+ }
+ }
+ Span* span;
+ if (insertedAt >= 0) {
+ span = fTs.insert(insertedAt);
+ } else {
+ insertedAt = tCount;
+ span = fTs.append();
+ }
+ span->fT = newT;
+ span->fOther = other;
+ span->fPt.fX = SK_ScalarNaN;
+ span->fWindSum = SK_MinS32;
+ span->fOppSum = SK_MinS32;
+ span->fWindValue = 1;
+ span->fOppValue = 0;
+ span->fTiny = false;
+ if ((span->fDone = newT == 1)) {
+ ++fDoneSpans;
+ }
+ span->fUnsortableStart = false;
+ span->fUnsortableEnd = false;
+ if (span - fTs.begin() > 0 && !span[-1].fDone
+ && !precisely_negative(newT - span[-1].fT)
+ // && approximately_negative(newT - span[-1].fT)
+ && xyAtT(&span[-1]) == xyAtT(span)) {
+ span[-1].fTiny = true;
+ span[-1].fDone = true;
+ if (approximately_negative(newT - span[-1].fT)) {
+ if (approximately_greater_than_one(newT)) {
+ span[-1].fUnsortableStart = true;
+ span[-2].fUnsortableEnd = true;
+ }
+ if (approximately_less_than_zero(span[-1].fT)) {
+ span->fUnsortableStart = true;
+ span[-1].fUnsortableEnd = true;
+ }
+ }
+ ++fDoneSpans;
+ }
+ if (fTs.end() - span > 1 && !span->fDone
+ && !precisely_negative(span[1].fT - newT)
+ // && approximately_negative(span[1].fT - newT)
+ && xyAtT(&span[1]) == xyAtT(span)) {
+ span->fTiny = true;
+ span->fDone = true;
+ if (approximately_negative(span[1].fT - newT)) {
+ if (approximately_greater_than_one(span[1].fT)) {
+ span->fUnsortableStart = true;
+ span[-1].fUnsortableEnd = true;
+ }
+ if (approximately_less_than_zero(newT)) {
+ span[1].fUnsortableStart = true;
+ span->fUnsortableEnd = true;
+ }
+ }
+ ++fDoneSpans;
+ }
+ return insertedAt;
+ }
+
+ // set spans from start to end to decrement by one
+ // note this walks other backwards
+ // FIMXE: there's probably an edge case that can be constructed where
+ // two span in one segment are separated by float epsilon on one span but
+ // not the other, if one segment is very small. For this
+ // case the counts asserted below may or may not be enough to separate the
+ // spans. Even if the counts work out, what if the spans aren't correctly
+ // sorted? It feels better in such a case to match the span's other span
+ // pointer since both coincident segments must contain the same spans.
+ void addTCancel(double startT, double endT, Segment& other,
+ double oStartT, double oEndT) {
+ SkASSERT(!approximately_negative(endT - startT));
+ SkASSERT(!approximately_negative(oEndT - oStartT));
+ bool binary = fOperand != other.fOperand;
+ int index = 0;
+ while (!approximately_negative(startT - fTs[index].fT)) {
+ ++index;
+ }
+ int oIndex = other.fTs.count();
+ while (approximately_positive(other.fTs[--oIndex].fT - oEndT))
+ ;
+ double tRatio = (oEndT - oStartT) / (endT - startT);
+ Span* test = &fTs[index];
+ Span* oTest = &other.fTs[oIndex];
+ SkTDArray<double> outsideTs;
+ SkTDArray<double> oOutsideTs;
+ do {
+ bool decrement = test->fWindValue && oTest->fWindValue && !binary;
+ bool track = test->fWindValue || oTest->fWindValue;
+ double testT = test->fT;
+ double oTestT = oTest->fT;
+ Span* span = test;
+ do {
+ if (decrement) {
+ decrementSpan(span);
+ } else if (track && span->fT < 1 && oTestT < 1) {
+ TrackOutside(outsideTs, span->fT, oTestT);
+ }
+ span = &fTs[++index];
+ } while (approximately_negative(span->fT - testT));
+ Span* oSpan = oTest;
+ double otherTMatchStart = oEndT - (span->fT - startT) * tRatio;
+ double otherTMatchEnd = oEndT - (test->fT - startT) * tRatio;
+ SkDEBUGCODE(int originalWindValue = oSpan->fWindValue);
+ while (approximately_negative(otherTMatchStart - oSpan->fT)
+ && !approximately_negative(otherTMatchEnd - oSpan->fT)) {
+ #ifdef SK_DEBUG
+ SkASSERT(originalWindValue == oSpan->fWindValue);
+ #endif
+ if (decrement) {
+ other.decrementSpan(oSpan);
+ } else if (track && oSpan->fT < 1 && testT < 1) {
+ TrackOutside(oOutsideTs, oSpan->fT, testT);
+ }
+ if (!oIndex) {
+ break;
+ }
+ oSpan = &other.fTs[--oIndex];
+ }
+ test = span;
+ oTest = oSpan;
+ } while (!approximately_negative(endT - test->fT));
+ SkASSERT(!oIndex || approximately_negative(oTest->fT - oStartT));
+ // FIXME: determine if canceled edges need outside ts added
+ if (!done() && outsideTs.count()) {
+ double tStart = outsideTs[0];
+ double oStart = outsideTs[1];
+ addCancelOutsides(tStart, oStart, other, oEndT);
+ int count = outsideTs.count();
+ if (count > 2) {
+ double tStart = outsideTs[count - 2];
+ double oStart = outsideTs[count - 1];
+ addCancelOutsides(tStart, oStart, other, oEndT);
+ }
+ }
+ if (!other.done() && oOutsideTs.count()) {
+ double tStart = oOutsideTs[0];
+ double oStart = oOutsideTs[1];
+ other.addCancelOutsides(tStart, oStart, *this, endT);
+ }
+ }
+
+ int bumpCoincidentThis(const Span* oTest, bool opp, int index,
+ SkTDArray<double>& outsideTs) {
+ int oWindValue = oTest->fWindValue;
+ int oOppValue = oTest->fOppValue;
+ if (opp) {
+ SkTSwap<int>(oWindValue, oOppValue);
+ }
+ Span* const test = &fTs[index];
+ Span* end = test;
+ const double oStartT = oTest->fT;
+ do {
+ if (bumpSpan(end, oWindValue, oOppValue)) {
+ TrackOutside(outsideTs, end->fT, oStartT);
+ }
+ end = &fTs[++index];
+ } while (approximately_negative(end->fT - test->fT));
+ return index;
+ }
+
+ // because of the order in which coincidences are resolved, this and other
+ // may not have the same intermediate points. Compute the corresponding
+ // intermediate T values (using this as the master, other as the follower)
+ // and walk other conditionally -- hoping that it catches up in the end
+ int bumpCoincidentOther(const Span* test, double oEndT, int& oIndex,
+ SkTDArray<double>& oOutsideTs) {
+ Span* const oTest = &fTs[oIndex];
+ Span* oEnd = oTest;
+ const double startT = test->fT;
+ const double oStartT = oTest->fT;
+ while (!approximately_negative(oEndT - oEnd->fT)
+ && approximately_negative(oEnd->fT - oStartT)) {
+ zeroSpan(oEnd);
+ TrackOutside(oOutsideTs, oEnd->fT, startT);
+ oEnd = &fTs[++oIndex];
+ }
+ return oIndex;
+ }
+
+ // FIXME: need to test this case:
+ // contourA has two segments that are coincident
+ // contourB has two segments that are coincident in the same place
+ // each ends up with +2/0 pairs for winding count
+ // since logic below doesn't transfer count (only increments/decrements) can this be
+ // resolved to +4/0 ?
+
+ // set spans from start to end to increment the greater by one and decrement
+ // the lesser
+ void addTCoincident(double startT, double endT, Segment& other, double oStartT, double oEndT) {
+ SkASSERT(!approximately_negative(endT - startT));
+ SkASSERT(!approximately_negative(oEndT - oStartT));
+ bool opp = fOperand ^ other.fOperand;
+ int index = 0;
+ while (!approximately_negative(startT - fTs[index].fT)) {
+ ++index;
+ }
+ int oIndex = 0;
+ while (!approximately_negative(oStartT - other.fTs[oIndex].fT)) {
+ ++oIndex;
+ }
+ Span* test = &fTs[index];
+ Span* oTest = &other.fTs[oIndex];
+ SkTDArray<double> outsideTs;
+ SkTDArray<double> oOutsideTs;
+ do {
+ // if either span has an opposite value and the operands don't match, resolve first
+ // SkASSERT(!test->fDone || !oTest->fDone);
+ if (test->fDone || oTest->fDone) {
+ index = advanceCoincidentThis(oTest, opp, index);
+ oIndex = other.advanceCoincidentOther(test, oEndT, oIndex);
+ } else {
+ index = bumpCoincidentThis(oTest, opp, index, outsideTs);
+ oIndex = other.bumpCoincidentOther(test, oEndT, oIndex, oOutsideTs);
+ }
+ test = &fTs[index];
+ oTest = &other.fTs[oIndex];
+ } while (!approximately_negative(endT - test->fT));
+ SkASSERT(approximately_negative(oTest->fT - oEndT));
+ SkASSERT(approximately_negative(oEndT - oTest->fT));
+ if (!done() && outsideTs.count()) {
+ addCoinOutsides(outsideTs, other, oEndT);
+ }
+ if (!other.done() && oOutsideTs.count()) {
+ other.addCoinOutsides(oOutsideTs, *this, endT);
+ }
+ }
+
+ // FIXME: this doesn't prevent the same span from being added twice
+ // fix in caller, assert here?
+ void addTPair(double t, Segment& other, double otherT, bool borrowWind) {
+ int tCount = fTs.count();
+ for (int tIndex = 0; tIndex < tCount; ++tIndex) {
+ const Span& span = fTs[tIndex];
+ if (!approximately_negative(span.fT - t)) {
+ break;
+ }
+ if (approximately_negative(span.fT - t) && span.fOther == &other
+ && approximately_equal(span.fOtherT, otherT)) {
+#if DEBUG_ADD_T_PAIR
+ SkDebugf("%s addTPair duplicate this=%d %1.9g other=%d %1.9g\n",
+ __FUNCTION__, fID, t, other.fID, otherT);
+#endif
+ return;
+ }
+ }
+#if DEBUG_ADD_T_PAIR
+ SkDebugf("%s addTPair this=%d %1.9g other=%d %1.9g\n",
+ __FUNCTION__, fID, t, other.fID, otherT);
+#endif
+ int insertedAt = addT(t, &other);
+ int otherInsertedAt = other.addT(otherT, this);
+ addOtherT(insertedAt, otherT, otherInsertedAt);
+ other.addOtherT(otherInsertedAt, t, insertedAt);
+ matchWindingValue(insertedAt, t, borrowWind);
+ other.matchWindingValue(otherInsertedAt, otherT, borrowWind);
+ }
+
+ void addTwoAngles(int start, int end, SkTDArray<Angle>& angles) const {
+ // add edge leading into junction
+ int min = SkMin32(end, start);
+ if (fTs[min].fWindValue > 0 || fTs[min].fOppValue > 0) {
+ addAngle(angles, end, start);
+ }
+ // add edge leading away from junction
+ int step = SkSign32(end - start);
+ int tIndex = nextExactSpan(end, step);
+ min = SkMin32(end, tIndex);
+ if (tIndex >= 0 && (fTs[min].fWindValue > 0 || fTs[min].fOppValue > 0)) {
+ addAngle(angles, end, tIndex);
+ }
+ }
+
+ int advanceCoincidentThis(const Span* oTest, bool opp, int index) {
+ Span* const test = &fTs[index];
+ Span* end = test;
+ do {
+ end = &fTs[++index];
+ } while (approximately_negative(end->fT - test->fT));
+ return index;
+ }
+
+ int advanceCoincidentOther(const Span* test, double oEndT, int& oIndex) {
+ Span* const oTest = &fTs[oIndex];
+ Span* oEnd = oTest;
+ const double oStartT = oTest->fT;
+ while (!approximately_negative(oEndT - oEnd->fT)
+ && approximately_negative(oEnd->fT - oStartT)) {
+ oEnd = &fTs[++oIndex];
+ }
+ return oIndex;
+ }
+
+ const Bounds& bounds() const {
+ return fBounds;
+ }
+
+ void buildAngles(int index, SkTDArray<Angle>& angles, bool includeOpp) const {
+ double referenceT = fTs[index].fT;
+ int lesser = index;
+ while (--lesser >= 0 && (includeOpp || fTs[lesser].fOther->fOperand == fOperand)
+ && precisely_negative(referenceT - fTs[lesser].fT)) {
+ buildAnglesInner(lesser, angles);
+ }
+ do {
+ buildAnglesInner(index, angles);
+ } while (++index < fTs.count() && (includeOpp || fTs[index].fOther->fOperand == fOperand)
+ && precisely_negative(fTs[index].fT - referenceT));
+ }
+
+ void buildAnglesInner(int index, SkTDArray<Angle>& angles) const {
+ Span* span = &fTs[index];
+ Segment* other = span->fOther;
+ // if there is only one live crossing, and no coincidence, continue
+ // in the same direction
+ // if there is coincidence, the only choice may be to reverse direction
+ // find edge on either side of intersection
+ int oIndex = span->fOtherIndex;
+ // if done == -1, prior span has already been processed
+ int step = 1;
+ int next = other->nextExactSpan(oIndex, step);
+ if (next < 0) {
+ step = -step;
+ next = other->nextExactSpan(oIndex, step);
+ }
+ // add candidate into and away from junction
+ other->addTwoAngles(next, oIndex, angles);
+ }
+
+ int computeSum(int startIndex, int endIndex, bool binary) {
+ SkTDArray<Angle> angles;
+ addTwoAngles(startIndex, endIndex, angles);
+ buildAngles(endIndex, angles, false);
+ // OPTIMIZATION: check all angles to see if any have computed wind sum
+ // before sorting (early exit if none)
+ SkTDArray<Angle*> sorted;
+ bool sortable = SortAngles(angles, sorted);
+#if DEBUG_SORT
+ sorted[0]->segment()->debugShowSort(__FUNCTION__, sorted, 0, 0, 0);
+#endif
+ if (!sortable) {
+ return SK_MinS32;
+ }
+ int angleCount = angles.count();
+ const Angle* angle;
+ const Segment* base;
+ int winding;
+ int oWinding;
+ int firstIndex = 0;
+ do {
+ angle = sorted[firstIndex];
+ base = angle->segment();
+ winding = base->windSum(angle);
+ if (winding != SK_MinS32) {
+ oWinding = base->oppSum(angle);
+ break;
+ }
+ if (++firstIndex == angleCount) {
+ return SK_MinS32;
+ }
+ } while (true);
+ // turn winding into contourWinding
+ int spanWinding = base->spanSign(angle);
+ bool inner = useInnerWinding(winding + spanWinding, winding);
+ #if DEBUG_WINDING
+ SkDebugf("%s spanWinding=%d winding=%d sign=%d inner=%d result=%d\n", __FUNCTION__,
+ spanWinding, winding, angle->sign(), inner,
+ inner ? winding + spanWinding : winding);
+ #endif
+ if (inner) {
+ winding += spanWinding;
+ }
+ #if DEBUG_SORT
+ base->debugShowSort(__FUNCTION__, sorted, firstIndex, winding, oWinding);
+ #endif
+ int nextIndex = firstIndex + 1;
+ int lastIndex = firstIndex != 0 ? firstIndex : angleCount;
+ winding -= base->spanSign(angle);
+ oWinding -= base->oppSign(angle);
+ do {
+ if (nextIndex == angleCount) {
+ nextIndex = 0;
+ }
+ angle = sorted[nextIndex];
+ Segment* segment = angle->segment();
+ bool opp = base->fOperand ^ segment->fOperand;
+ int maxWinding, oMaxWinding;
+ int spanSign = segment->spanSign(angle);
+ int oppoSign = segment->oppSign(angle);
+ if (opp) {
+ oMaxWinding = oWinding;
+ oWinding -= spanSign;
+ maxWinding = winding;
+ if (oppoSign) {
+ winding -= oppoSign;
+ }
+ } else {
+ maxWinding = winding;
+ winding -= spanSign;
+ oMaxWinding = oWinding;
+ if (oppoSign) {
+ oWinding -= oppoSign;
+ }
+ }
+ if (segment->windSum(angle) == SK_MinS32) {
+ if (opp) {
+ if (useInnerWinding(oMaxWinding, oWinding)) {
+ oMaxWinding = oWinding;
+ }
+ if (oppoSign && useInnerWinding(maxWinding, winding)) {
+ maxWinding = winding;
+ }
+ (void) segment->markAndChaseWinding(angle, oMaxWinding, maxWinding);
+ } else {
+ if (useInnerWinding(maxWinding, winding)) {
+ maxWinding = winding;
+ }
+ if (oppoSign && useInnerWinding(oMaxWinding, oWinding)) {
+ oMaxWinding = oWinding;
+ }
+ (void) segment->markAndChaseWinding(angle, maxWinding,
+ binary ? oMaxWinding : 0);
+ }
+ }
+ } while (++nextIndex != lastIndex);
+ int minIndex = SkMin32(startIndex, endIndex);
+ return windSum(minIndex);
+ }
+
+ int crossedSpanX(const SkPoint& basePt, SkScalar& bestX, double& hitT, bool opp) const {
+ int bestT = -1;
+ SkScalar left = bounds().fLeft;
+ SkScalar right = bounds().fRight;
+ int end = 0;
+ do {
+ int start = end;
+ end = nextSpan(start, 1);
+ if ((opp ? fTs[start].fOppValue : fTs[start].fWindValue) == 0) {
+ continue;
+ }
+ SkPoint edge[4];
+ double startT = fTs[start].fT;
+ double endT = fTs[end].fT;
+ (*SegmentSubDivide[fVerb])(fPts, startT, endT, edge);
+ // intersect ray starting at basePt with edge
+ Intersections intersections;
+ // FIXME: always use original and limit results to T values within
+ // start t and end t.
+ // OPTIMIZE: use specialty function that intersects ray with curve,
+ // returning t values only for curve (we don't care about t on ray)
+ int pts = (*HSegmentIntersect[fVerb])(edge, left, right, basePt.fY,
+ false, intersections);
+ if (pts == 0) {
+ continue;
+ }
+ if (pts > 1 && fVerb == SkPath::kLine_Verb) {
+ // if the intersection is edge on, wait for another one
+ continue;
+ }
+ for (int index = 0; index < pts; ++index) {
+ double foundT = intersections.fT[0][index];
+ double testT = startT + (endT - startT) * foundT;
+ SkScalar testX = (*SegmentXAtT[fVerb])(fPts, testT);
+ if (bestX < testX && testX < basePt.fX) {
+ if (fVerb > SkPath::kLine_Verb
+ && !approximately_less_than_zero(foundT)
+ && !approximately_greater_than_one(foundT)) {
+ SkScalar dy = (*SegmentDYAtT[fVerb])(fPts, testT);
+ if (approximately_zero(dy)) {
+ continue;
+ }
+ }
+ bestX = testX;
+ bestT = foundT < 1 ? start : end;
+ hitT = testT;
+ }
+ }
+ } while (fTs[end].fT != 1);
+ return bestT;
+ }
+
+ int crossedSpanY(const SkPoint& basePt, SkScalar& bestY, double& hitT, bool opp) const {
+ int bestT = -1;
+ SkScalar top = bounds().fTop;
+ SkScalar bottom = bounds().fBottom;
+ int end = 0;
+ do {
+ int start = end;
+ end = nextSpan(start, 1);
+ if ((opp ? fTs[start].fOppValue : fTs[start].fWindValue) == 0) {
+ continue;
+ }
+ SkPoint edge[4];
+ double startT = fTs[start].fT;
+ double endT = fTs[end].fT;
+ (*SegmentSubDivide[fVerb])(fPts, startT, endT, edge);
+ // intersect ray starting at basePt with edge
+ Intersections intersections;
+ // FIXME: always use original and limit results to T values within
+ // start t and end t.
+ // OPTIMIZE: use specialty function that intersects ray with curve,
+ // returning t values only for curve (we don't care about t on ray)
+ int pts = (*VSegmentIntersect[fVerb])(edge, top, bottom, basePt.fX,
+ false, intersections);
+ if (pts == 0) {
+ continue;
+ }
+ if (pts > 1 && fVerb == SkPath::kLine_Verb) {
+ // if the intersection is edge on, wait for another one
+ continue;
+ }
+ for (int index = 0; index < pts; ++index) {
+ double foundT = intersections.fT[0][index];
+ double testT = startT + (endT - startT) * foundT;
+ SkScalar testY = (*SegmentYAtT[fVerb])(fPts, testT);
+ if (bestY < testY && testY < basePt.fY) {
+ if (fVerb > SkPath::kLine_Verb
+ && !approximately_less_than_zero(foundT)
+ && !approximately_greater_than_one(foundT)) {
+ SkScalar dx = (*SegmentDXAtT[fVerb])(fPts, testT);
+ if (approximately_zero(dx)) {
+ continue;
+ }
+ }
+ bestY = testY;
+ bestT = foundT < 1 ? start : end;
+ hitT = testT;
+ }
+ }
+ } while (fTs[end].fT != 1);
+ return bestT;
+ }
+
+ void decrementSpan(Span* span) {
+ SkASSERT(span->fWindValue > 0);
+ if (--(span->fWindValue) == 0) {
+ if (!span->fOppValue && !span->fDone) {
+ span->fDone = true;
+ ++fDoneSpans;
+ }
+ }
+ }
+
+ bool bumpSpan(Span* span, int windDelta, int oppDelta) {
+ SkASSERT(!span->fDone);
+ span->fWindValue += windDelta;
+ SkASSERT(span->fWindValue >= 0);
+ span->fOppValue += oppDelta;
+ SkASSERT(span->fOppValue >= 0);
+ if (fXor) {
+ span->fWindValue &= 1;
+ }
+ if (fOppXor) {
+ span->fOppValue &= 1;
+ }
+ if (!span->fWindValue && !span->fOppValue) {
+ span->fDone = true;
+ ++fDoneSpans;
+ return true;
+ }
+ return false;
+ }
+
+ // OPTIMIZE
+ // when the edges are initially walked, they don't automatically get the prior and next
+ // edges assigned to positions t=0 and t=1. Doing that would remove the need for this check,
+ // and would additionally remove the need for similar checks in condition edges. It would
+ // also allow intersection code to assume end of segment intersections (maybe?)
+ bool complete() const {
+ int count = fTs.count();
+ return count > 1 && fTs[0].fT == 0 && fTs[--count].fT == 1;
+ }
+
+ bool done() const {
+ SkASSERT(fDoneSpans <= fTs.count());
+ return fDoneSpans == fTs.count();
+ }
+
+ bool done(int min) const {
+ return fTs[min].fDone;
+ }
+
+ bool done(const Angle* angle) const {
+ return done(SkMin32(angle->start(), angle->end()));
+ }
+
+ /*
+ The M and S variable name parts stand for the operators.
+ Mi stands for Minuend (see wiki subtraction, analogous to difference)
+ Su stands for Subtrahend
+ The Opp variable name part designates that the value is for the Opposite operator.
+ Opposite values result from combining coincident spans.
+ */
+
+ Segment* findNextOp(SkTDArray<Span*>& chase, int& nextStart, int& nextEnd,
+ bool& unsortable, ShapeOp op, const int xorMiMask, const int xorSuMask) {
+ const int startIndex = nextStart;
+ const int endIndex = nextEnd;
+ SkASSERT(startIndex != endIndex);
+ const int count = fTs.count();
+ SkASSERT(startIndex < endIndex ? startIndex < count - 1 : startIndex > 0);
+ const int step = SkSign32(endIndex - startIndex);
+ const int end = nextExactSpan(startIndex, step);
+ SkASSERT(end >= 0);
+ Span* endSpan = &fTs[end];
+ Segment* other;
+ if (isSimple(end)) {
+ // mark the smaller of startIndex, endIndex done, and all adjacent
+ // spans with the same T value (but not 'other' spans)
+ #if DEBUG_WINDING
+ SkDebugf("%s simple\n", __FUNCTION__);
+ #endif
+ int min = SkMin32(startIndex, endIndex);
+ if (fTs[min].fDone) {
+ return NULL;
+ }
+ markDoneBinary(min);
+ other = endSpan->fOther;
+ nextStart = endSpan->fOtherIndex;
+ double startT = other->fTs[nextStart].fT;
+ nextEnd = nextStart;
+ do {
+ nextEnd += step;
+ }
+ while (precisely_zero(startT - other->fTs[nextEnd].fT));
+ SkASSERT(step < 0 ? nextEnd >= 0 : nextEnd < other->fTs.count());
+ return other;
+ }
+ // more than one viable candidate -- measure angles to find best
+ SkTDArray<Angle> angles;
+ SkASSERT(startIndex - endIndex != 0);
+ SkASSERT((startIndex - endIndex < 0) ^ (step < 0));
+ addTwoAngles(startIndex, end, angles);
+ buildAngles(end, angles, true);
+ SkTDArray<Angle*> sorted;
+ bool sortable = SortAngles(angles, sorted);
+ int angleCount = angles.count();
+ int firstIndex = findStartingEdge(sorted, startIndex, end);
+ SkASSERT(firstIndex >= 0);
+ #if DEBUG_SORT
+ debugShowSort(__FUNCTION__, sorted, firstIndex);
+ #endif
+ if (!sortable) {
+ unsortable = true;
+ return NULL;
+ }
+ SkASSERT(sorted[firstIndex]->segment() == this);
+ #if DEBUG_WINDING
+ SkDebugf("%s firstIndex=[%d] sign=%d\n", __FUNCTION__, firstIndex,
+ sorted[firstIndex]->sign());
+ #endif
+ int sumMiWinding = updateWinding(endIndex, startIndex);
+ int sumSuWinding = updateOppWinding(endIndex, startIndex);
+ if (operand()) {
+ SkTSwap<int>(sumMiWinding, sumSuWinding);
+ }
+ int nextIndex = firstIndex + 1;
+ int lastIndex = firstIndex != 0 ? firstIndex : angleCount;
+ const Angle* foundAngle = NULL;
+ bool foundDone = false;
+ // iterate through the angle, and compute everyone's winding
+ Segment* nextSegment;
+ do {
+ SkASSERT(nextIndex != firstIndex);
+ if (nextIndex == angleCount) {
+ nextIndex = 0;
+ }
+ const Angle* nextAngle = sorted[nextIndex];
+ nextSegment = nextAngle->segment();
+ int maxWinding, sumWinding, oppMaxWinding, oppSumWinding;
+ bool activeAngle = nextSegment->activeOp(xorMiMask, xorSuMask, nextAngle->start(),
+ nextAngle->end(), op, sumMiWinding, sumSuWinding,
+ maxWinding, sumWinding, oppMaxWinding, oppSumWinding);
+ if (activeAngle && (!foundAngle || foundDone)) {
+ foundAngle = nextAngle;
+ foundDone = nextSegment->done(nextAngle) && !nextSegment->tiny(nextAngle);
+ }
+ if (nextSegment->done()) {
+ continue;
+ }
+ if (nextSegment->windSum(nextAngle) != SK_MinS32) {
+ continue;
+ }
+ Span* last = nextSegment->markAngle(maxWinding, sumWinding, oppMaxWinding,
+ oppSumWinding, activeAngle, nextAngle);
+ if (last) {
+ *chase.append() = last;
+#if DEBUG_WINDING
+ SkDebugf("%s chase.append id=%d\n", __FUNCTION__,
+ last->fOther->fTs[last->fOtherIndex].fOther->debugID());
+#endif
+ }
+ } while (++nextIndex != lastIndex);
+ markDoneBinary(SkMin32(startIndex, endIndex));
+ if (!foundAngle) {
+ return NULL;
+ }
+ nextStart = foundAngle->start();
+ nextEnd = foundAngle->end();
+ nextSegment = foundAngle->segment();
+
+ #if DEBUG_WINDING
+ SkDebugf("%s from:[%d] to:[%d] start=%d end=%d\n",
+ __FUNCTION__, debugID(), nextSegment->debugID(), nextStart, nextEnd);
+ #endif
+ return nextSegment;
+ }
+
+ // so the span needs to contain the pairing info found here
+ // this should include the winding computed for the edge, and
+ // what edge it connects to, and whether it is discarded
+ // (maybe discarded == abs(winding) > 1) ?
+ // only need derivatives for duration of sorting, add a new struct
+ // for pairings, remove extra spans that have zero length and
+ // reference an unused other
+ // for coincident, the last span on the other may be marked done
+ // (always?)
+
+ // if loop is exhausted, contour may be closed.
+ // FIXME: pass in close point so we can check for closure
+
+ // given a segment, and a sense of where 'inside' is, return the next
+ // segment. If this segment has an intersection, or ends in multiple
+ // segments, find the mate that continues the outside.
+ // note that if there are multiples, but no coincidence, we can limit
+ // choices to connections in the correct direction
+
+ // mark found segments as done
+
+ // start is the index of the beginning T of this edge
+ // it is guaranteed to have an end which describes a non-zero length (?)
+ // winding -1 means ccw, 1 means cw
+ Segment* findNextWinding(SkTDArray<Span*>& chase, bool active,
+ int& nextStart, int& nextEnd, int& winding, int& spanWinding,
+ bool& unsortable) {
+ const int startIndex = nextStart;
+ const int endIndex = nextEnd;
+ int outerWinding = winding;
+ int innerWinding = winding + spanWinding;
+ #if DEBUG_WINDING
+ SkDebugf("%s winding=%d spanWinding=%d outerWinding=%d innerWinding=%d\n",
+ __FUNCTION__, winding, spanWinding, outerWinding, innerWinding);
+ #endif
+ if (useInnerWinding(outerWinding, innerWinding)) {
+ outerWinding = innerWinding;
+ }
+ SkASSERT(startIndex != endIndex);
+ int count = fTs.count();
+ SkASSERT(startIndex < endIndex ? startIndex < count - 1
+ : startIndex > 0);
+ int step = SkSign32(endIndex - startIndex);
+ int end = nextExactSpan(startIndex, step);
+ SkASSERT(end >= 0);
+ Span* endSpan = &fTs[end];
+ Segment* other;
+ if (isSimple(end)) {
+ // mark the smaller of startIndex, endIndex done, and all adjacent
+ // spans with the same T value (but not 'other' spans)
+ #if DEBUG_WINDING
+ SkDebugf("%s simple\n", __FUNCTION__);
+ #endif
+ int min = SkMin32(startIndex, endIndex);
+ if (fTs[min].fDone) {
+ return NULL;
+ }
+ markDone(min, outerWinding);
+ other = endSpan->fOther;
+ nextStart = endSpan->fOtherIndex;
+ double startT = other->fTs[nextStart].fT;
+ nextEnd = nextStart;
+ do {
+ nextEnd += step;
+ }
+ while (precisely_zero(startT - other->fTs[nextEnd].fT));
+ SkASSERT(step < 0 ? nextEnd >= 0 : nextEnd < other->fTs.count());
+ return other;
+ }
+ // more than one viable candidate -- measure angles to find best
+ SkTDArray<Angle> angles;
+ SkASSERT(startIndex - endIndex != 0);
+ SkASSERT((startIndex - endIndex < 0) ^ (step < 0));
+ addTwoAngles(startIndex, end, angles);
+ buildAngles(end, angles, false);
+ SkTDArray<Angle*> sorted;
+ bool sortable = SortAngles(angles, sorted);
+ int angleCount = angles.count();
+ int firstIndex = findStartingEdge(sorted, startIndex, end);
+ SkASSERT(firstIndex >= 0);
+ #if DEBUG_SORT
+ debugShowSort(__FUNCTION__, sorted, firstIndex, winding, 0);
+ #endif
+ if (!sortable) {
+ unsortable = true;
+ return NULL;
+ }
+ SkASSERT(sorted[firstIndex]->segment() == this);
+ #if DEBUG_WINDING
+ SkDebugf("%s [%d] sign=%d\n", __FUNCTION__, firstIndex, sorted[firstIndex]->sign());
+ #endif
+ int sumWinding = winding - spanSign(sorted[firstIndex]);
+ int nextIndex = firstIndex + 1;
+ int lastIndex = firstIndex != 0 ? firstIndex : angleCount;
+ const Angle* foundAngle = NULL;
+ // FIXME: found done logic probably fails if there are more than 4
+ // sorted angles. It should bias towards the first and last undone
+ // edges -- but not sure that it won't choose a middle (incorrect)
+ // edge if one is undone
+ bool foundDone = false;
+ bool foundDone2 = false;
+ // iterate through the angle, and compute everyone's winding
+ bool altFlipped = false;
+ bool foundFlipped = false;
+ int foundSum = SK_MinS32;
+ Segment* nextSegment;
+ int lastNonZeroSum = winding;
+ do {
+ if (nextIndex == angleCount) {
+ nextIndex = 0;
+ }
+ const Angle* nextAngle = sorted[nextIndex];
+ int maxWinding = sumWinding;
+ if (sumWinding) {
+ lastNonZeroSum = sumWinding;
+ }
+ nextSegment = nextAngle->segment();
+ bool nextDone = nextSegment->done(nextAngle);
+ bool nextTiny = nextSegment->tiny(nextAngle);
+ sumWinding -= nextSegment->spanSign(nextAngle);
+ altFlipped ^= lastNonZeroSum * sumWinding < 0; // flip if different signs
+ #if 0 && DEBUG_WINDING
+ SkASSERT(abs(sumWinding) <= gDebugMaxWindSum);
+ SkDebugf("%s [%d] maxWinding=%d sumWinding=%d sign=%d altFlipped=%d\n", __FUNCTION__,
+ nextIndex, maxWinding, sumWinding, nextAngle->sign(), altFlipped);
+ #endif
+ if (!sumWinding) {
+ if (!active) {
+ // FIXME: shouldn't this call mark and chase done ?
+ markDone(SkMin32(startIndex, endIndex), outerWinding);
+ // FIXME: shouldn't this call mark and chase winding ?
+ nextSegment->markWinding(SkMin32(nextAngle->start(),
+ nextAngle->end()), maxWinding);
+ #if DEBUG_WINDING
+ SkDebugf("%s [%d] inactive\n", __FUNCTION__, nextIndex);
+ #endif
+ return NULL;
+ }
+ if (!foundAngle || foundDone) {
+ foundAngle = nextAngle;
+ foundDone = nextDone && !nextTiny;
+ foundFlipped = altFlipped;
+ }
+ continue;
+ }
+
+ if (!maxWinding && (!foundAngle || foundDone2)) {
+ #if DEBUG_WINDING
+ if (foundAngle && foundDone2) {
+ SkDebugf("%s [%d] !foundAngle && foundDone2\n", __FUNCTION__, nextIndex);
+ }
+ #endif
+ foundAngle = nextAngle;
+ foundDone2 = nextDone && !nextTiny;
+ foundFlipped = altFlipped;
+ foundSum = sumWinding;
+ }
+ if (nextSegment->done()) {
+ continue;
+ }
+ // if the winding is non-zero, nextAngle does not connect to
+ // current chain. If we haven't done so already, mark the angle
+ // as done, record the winding value, and mark connected unambiguous
+ // segments as well.
+ if (nextSegment->windSum(nextAngle) == SK_MinS32) {
+ if (useInnerWinding(maxWinding, sumWinding)) {
+ maxWinding = sumWinding;
+ }
+ Span* last;
+ if (foundAngle) {
+ last = nextSegment->markAndChaseWinding(nextAngle, maxWinding);
+ } else {
+ last = nextSegment->markAndChaseDone(nextAngle, maxWinding);
+ }
+ if (last) {
+ *chase.append() = last;
+ #if DEBUG_WINDING
+ SkDebugf("%s chase.append id=%d\n", __FUNCTION__,
+ last->fOther->fTs[last->fOtherIndex].fOther->debugID());
+ #endif
+ }
+ }
+ } while (++nextIndex != lastIndex);
+ markDone(SkMin32(startIndex, endIndex), outerWinding);
+ if (!foundAngle) {
+ return NULL;
+ }
+ nextStart = foundAngle->start();
+ nextEnd = foundAngle->end();
+ nextSegment = foundAngle->segment();
+ int flipped = foundFlipped ? -1 : 1;
+ spanWinding = SkSign32(spanWinding) * flipped * nextSegment->windValue(
+ SkMin32(nextStart, nextEnd));
+ if (winding) {
+ #if DEBUG_WINDING
+ SkDebugf("%s ---6 winding=%d foundSum=", __FUNCTION__, winding);
+ if (foundSum == SK_MinS32) {
+ SkDebugf("?");
+ } else {
+ SkDebugf("%d", foundSum);
+ }
+ SkDebugf("\n");
+ #endif
+ winding = foundSum;
+ }
+ #if DEBUG_WINDING
+ SkDebugf("%s spanWinding=%d flipped=%d\n", __FUNCTION__, spanWinding, flipped);
+ #endif
+ return nextSegment;
+ }
+
+ Segment* findNextWinding(SkTDArray<Span*>& chase, int& nextStart, int& nextEnd,
+ bool& unsortable) {
+ const int startIndex = nextStart;
+ const int endIndex = nextEnd;
+ SkASSERT(startIndex != endIndex);
+ const int count = fTs.count();
+ SkASSERT(startIndex < endIndex ? startIndex < count - 1 : startIndex > 0);
+ const int step = SkSign32(endIndex - startIndex);
+ const int end = nextExactSpan(startIndex, step);
+ SkASSERT(end >= 0);
+ Span* endSpan = &fTs[end];
+ Segment* other;
+ if (isSimple(end)) {
+ // mark the smaller of startIndex, endIndex done, and all adjacent
+ // spans with the same T value (but not 'other' spans)
+ #if DEBUG_WINDING
+ SkDebugf("%s simple\n", __FUNCTION__);
+ #endif
+ int min = SkMin32(startIndex, endIndex);
+ if (fTs[min].fDone) {
+ return NULL;
+ }
+ markDoneUnary(min);
+ other = endSpan->fOther;
+ nextStart = endSpan->fOtherIndex;
+ double startT = other->fTs[nextStart].fT;
+ nextEnd = nextStart;
+ do {
+ nextEnd += step;
+ }
+ while (precisely_zero(startT - other->fTs[nextEnd].fT));
+ SkASSERT(step < 0 ? nextEnd >= 0 : nextEnd < other->fTs.count());
+ return other;
+ }
+ // more than one viable candidate -- measure angles to find best
+ SkTDArray<Angle> angles;
+ SkASSERT(startIndex - endIndex != 0);
+ SkASSERT((startIndex - endIndex < 0) ^ (step < 0));
+ addTwoAngles(startIndex, end, angles);
+ buildAngles(end, angles, true);
+ SkTDArray<Angle*> sorted;
+ bool sortable = SortAngles(angles, sorted);
+ int angleCount = angles.count();
+ int firstIndex = findStartingEdge(sorted, startIndex, end);
+ SkASSERT(firstIndex >= 0);
+ #if DEBUG_SORT
+ debugShowSort(__FUNCTION__, sorted, firstIndex);
+ #endif
+ if (!sortable) {
+ unsortable = true;
+ return NULL;
+ }
+ SkASSERT(sorted[firstIndex]->segment() == this);
+ #if DEBUG_WINDING
+ SkDebugf("%s firstIndex=[%d] sign=%d\n", __FUNCTION__, firstIndex,
+ sorted[firstIndex]->sign());
+ #endif
+ int sumWinding = updateWinding(endIndex, startIndex);
+ int outside = sumWinding & 1; // associate pairs together to avoid figure eights
+ int nextIndex = firstIndex + 1;
+ int lastIndex = firstIndex != 0 ? firstIndex : angleCount;
+ const Angle* foundAngle = NULL;
+ bool foundDone = false;
+ // iterate through the angle, and compute everyone's winding
+ Segment* nextSegment;
+ do {
+ SkASSERT(nextIndex != firstIndex);
+ if (nextIndex == angleCount) {
+ nextIndex = 0;
+ }
+ const Angle* nextAngle = sorted[nextIndex];
+ nextSegment = nextAngle->segment();
+ int maxWinding;
+ bool activeAngle = nextSegment->activeWinding(nextAngle->start(), nextAngle->end(),
+ maxWinding, sumWinding);
+ if (activeAngle && (!foundAngle || foundDone) && outside != (sumWinding & 1)) {
+ foundAngle = nextAngle;
+ foundDone = nextSegment->done(nextAngle) && !nextSegment->tiny(nextAngle);
+ }
+ if (nextSegment->done()) {
+ continue;
+ }
+ if (nextSegment->windSum(nextAngle) != SK_MinS32) {
+ continue;
+ }
+ Span* last = nextSegment->markAngle(maxWinding, sumWinding, activeAngle, nextAngle);
+ if (last) {
+ *chase.append() = last;
+#if DEBUG_WINDING
+ SkDebugf("%s chase.append id=%d\n", __FUNCTION__,
+ last->fOther->fTs[last->fOtherIndex].fOther->debugID());
+#endif
+ }
+ } while (++nextIndex != lastIndex);
+ markDoneUnary(SkMin32(startIndex, endIndex));
+ if (!foundAngle) {
+ return NULL;
+ }
+ nextStart = foundAngle->start();
+ nextEnd = foundAngle->end();
+ nextSegment = foundAngle->segment();
+
+ #if DEBUG_WINDING
+ SkDebugf("%s from:[%d] to:[%d] start=%d end=%d\n",
+ __FUNCTION__, debugID(), nextSegment->debugID(), nextStart, nextEnd);
+ #endif
+ return nextSegment;
+ }
+
+ Segment* findNextXor(int& nextStart, int& nextEnd, bool& unsortable) {
+ const int startIndex = nextStart;
+ const int endIndex = nextEnd;
+ SkASSERT(startIndex != endIndex);
+ int count = fTs.count();
+ SkASSERT(startIndex < endIndex ? startIndex < count - 1
+ : startIndex > 0);
+ int step = SkSign32(endIndex - startIndex);
+ int end = nextExactSpan(startIndex, step);
+ SkASSERT(end >= 0);
+ Span* endSpan = &fTs[end];
+ Segment* other;
+ if (isSimple(end)) {
+ #if DEBUG_WINDING
+ SkDebugf("%s simple\n", __FUNCTION__);
+ #endif
+ int min = SkMin32(startIndex, endIndex);
+ if (fTs[min].fDone) {
+ return NULL;
+ }
+ markDone(min, 1);
+ other = endSpan->fOther;
+ nextStart = endSpan->fOtherIndex;
+ double startT = other->fTs[nextStart].fT;
+ SkDEBUGCODE(bool firstLoop = true;)
+ if ((approximately_less_than_zero(startT) && step < 0)
+ || (approximately_greater_than_one(startT) && step > 0)) {
+ step = -step;
+ SkDEBUGCODE(firstLoop = false;)
+ }
+ do {
+ nextEnd = nextStart;
+ do {
+ nextEnd += step;
+ }
+ while (precisely_zero(startT - other->fTs[nextEnd].fT));
+ if (other->fTs[SkMin32(nextStart, nextEnd)].fWindValue) {
+ break;
+ }
+ #ifdef SK_DEBUG
+ SkASSERT(firstLoop);
+ #endif
+ SkDEBUGCODE(firstLoop = false;)
+ step = -step;
+ } while (true);
+ SkASSERT(step < 0 ? nextEnd >= 0 : nextEnd < other->fTs.count());
+ return other;
+ }
+ SkTDArray<Angle> angles;
+ SkASSERT(startIndex - endIndex != 0);
+ SkASSERT((startIndex - endIndex < 0) ^ (step < 0));
+ addTwoAngles(startIndex, end, angles);
+ buildAngles(end, angles, false);
+ SkTDArray<Angle*> sorted;
+ bool sortable = SortAngles(angles, sorted);
+ if (!sortable) {
+ unsortable = true;
+ return NULL;
+ }
+ int angleCount = angles.count();
+ int firstIndex = findStartingEdge(sorted, startIndex, end);
+ SkASSERT(firstIndex >= 0);
+ #if DEBUG_SORT
+ debugShowSort(__FUNCTION__, sorted, firstIndex, 0, 0);
+ #endif
+ SkASSERT(sorted[firstIndex]->segment() == this);
+ int nextIndex = firstIndex + 1;
+ int lastIndex = firstIndex != 0 ? firstIndex : angleCount;
+ const Angle* nextAngle;
+ Segment* nextSegment;
+ bool foundAngle = false;
+ do {
+ if (nextIndex == angleCount) {
+ nextIndex = 0;
+ }
+ nextAngle = sorted[nextIndex];
+ nextSegment = nextAngle->segment();
+ if (!nextSegment->done(nextAngle) || nextSegment->tiny(nextAngle)) {
+ foundAngle = true;
+ break;
+ }
+ } while (++nextIndex != lastIndex);
+ markDone(SkMin32(startIndex, endIndex), 1);
+ if (!foundAngle) {
+ nextIndex = firstIndex + 1 == angleCount ? 0 : firstIndex + 1;
+ nextAngle = sorted[nextIndex];
+ }
+ nextStart = nextAngle->start();
+ nextEnd = nextAngle->end();
+ return nextSegment;
+ }
+
+ int findStartingEdge(SkTDArray<Angle*>& sorted, int start, int end) {
+ int angleCount = sorted.count();
+ int firstIndex = -1;
+ for (int angleIndex = 0; angleIndex < angleCount; ++angleIndex) {
+ const Angle* angle = sorted[angleIndex];
+ if (angle->segment() == this && angle->start() == end &&
+ angle->end() == start) {
+ firstIndex = angleIndex;
+ break;
+ }
+ }
+ return firstIndex;
+ }
+
+ // FIXME: this is tricky code; needs its own unit test
+ void findTooCloseToCall() {
+ int count = fTs.count();
+ if (count < 3) { // require t=0, x, 1 at minimum
+ return;
+ }
+ int matchIndex = 0;
+ int moCount;
+ Span* match;
+ Segment* mOther;
+ do {
+ match = &fTs[matchIndex];
+ mOther = match->fOther;
+ // FIXME: allow quads, cubics to be near coincident?
+ if (mOther->fVerb == SkPath::kLine_Verb) {
+ moCount = mOther->fTs.count();
+ if (moCount >= 3) {
+ break;
+ }
+ }
+ if (++matchIndex >= count) {
+ return;
+ }
+ } while (true); // require t=0, x, 1 at minimum
+ // OPTIMIZATION: defer matchPt until qualifying toCount is found?
+ const SkPoint* matchPt = &xyAtT(match);
+ // look for a pair of nearby T values that map to the same (x,y) value
+ // if found, see if the pair of other segments share a common point. If
+ // so, the span from here to there is coincident.
+ for (int index = matchIndex + 1; index < count; ++index) {
+ Span* test = &fTs[index];
+ if (test->fDone) {
+ continue;
+ }
+ Segment* tOther = test->fOther;
+ if (tOther->fVerb != SkPath::kLine_Verb) {
+ continue; // FIXME: allow quads, cubics to be near coincident?
+ }
+ int toCount = tOther->fTs.count();
+ if (toCount < 3) { // require t=0, x, 1 at minimum
+ continue;
+ }
+ const SkPoint* testPt = &xyAtT(test);
+ if (*matchPt != *testPt) {
+ matchIndex = index;
+ moCount = toCount;
+ match = test;
+ mOther = tOther;
+ matchPt = testPt;
+ continue;
+ }
+ int moStart = -1;
+ int moEnd = -1;
+ double moStartT, moEndT;
+ for (int moIndex = 0; moIndex < moCount; ++moIndex) {
+ Span& moSpan = mOther->fTs[moIndex];
+ if (moSpan.fDone) {
+ continue;
+ }
+ if (moSpan.fOther == this) {
+ if (moSpan.fOtherT == match->fT) {
+ moStart = moIndex;
+ moStartT = moSpan.fT;
+ }
+ continue;
+ }
+ if (moSpan.fOther == tOther) {
+ if (tOther->fTs[moSpan.fOtherIndex].fWindValue == 0) {
+ moStart = -1;
+ break;
+ }
+ SkASSERT(moEnd == -1);
+ moEnd = moIndex;
+ moEndT = moSpan.fT;
+ }
+ }
+ if (moStart < 0 || moEnd < 0) {
+ continue;
+ }
+ // FIXME: if moStartT, moEndT are initialized to NaN, can skip this test
+ if (approximately_equal(moStartT, moEndT)) {
+ continue;
+ }
+ int toStart = -1;
+ int toEnd = -1;
+ double toStartT, toEndT;
+ for (int toIndex = 0; toIndex < toCount; ++toIndex) {
+ Span& toSpan = tOther->fTs[toIndex];
+ if (toSpan.fDone) {
+ continue;
+ }
+ if (toSpan.fOther == this) {
+ if (toSpan.fOtherT == test->fT) {
+ toStart = toIndex;
+ toStartT = toSpan.fT;
+ }
+ continue;
+ }
+ if (toSpan.fOther == mOther && toSpan.fOtherT == moEndT) {
+ if (mOther->fTs[toSpan.fOtherIndex].fWindValue == 0) {
+ moStart = -1;
+ break;
+ }
+ SkASSERT(toEnd == -1);
+ toEnd = toIndex;
+ toEndT = toSpan.fT;
+ }
+ }
+ // FIXME: if toStartT, toEndT are initialized to NaN, can skip this test
+ if (toStart <= 0 || toEnd <= 0) {
+ continue;
+ }
+ if (approximately_equal(toStartT, toEndT)) {
+ continue;
+ }
+ // test to see if the segment between there and here is linear
+ if (!mOther->isLinear(moStart, moEnd)
+ || !tOther->isLinear(toStart, toEnd)) {
+ continue;
+ }
+ bool flipped = (moStart - moEnd) * (toStart - toEnd) < 1;
+ if (flipped) {
+ mOther->addTCancel(moStartT, moEndT, *tOther, toEndT, toStartT);
+ } else {
+ mOther->addTCoincident(moStartT, moEndT, *tOther, toStartT, toEndT);
+ }
+ }
+ }
+
+ // start here;
+ // either:
+ // a) mark spans with either end unsortable as done, or
+ // b) rewrite findTop / findTopSegment / findTopContour to iterate further
+ // when encountering an unsortable span
+
+ // OPTIMIZATION : for a pair of lines, can we compute points at T (cached)
+ // and use more concise logic like the old edge walker code?
+ // FIXME: this needs to deal with coincident edges
+ Segment* findTop(int& tIndex, int& endIndex, bool& unsortable, bool onlySortable) {
+ // iterate through T intersections and return topmost
+ // topmost tangent from y-min to first pt is closer to horizontal
+ SkASSERT(!done());
+ int firstT = -1;
+ SkPoint topPt;
+ topPt.fY = SK_ScalarMax;
+ int count = fTs.count();
+ // see if either end is not done since we want smaller Y of the pair
+ bool lastDone = true;
+ bool lastUnsortable = false;
+ for (int index = 0; index < count; ++index) {
+ const Span& span = fTs[index];
+ if (onlySortable && (span.fUnsortableStart || lastUnsortable)) {
+ goto next;
+ }
+ if (!span.fDone | !lastDone) {
+ const SkPoint& intercept = xyAtT(&span);
+ if (topPt.fY > intercept.fY || (topPt.fY == intercept.fY
+ && topPt.fX > intercept.fX)) {
+ topPt = intercept;
+ firstT = index;
+ }
+ }
+ next:
+ lastDone = span.fDone;
+ lastUnsortable = span.fUnsortableEnd;
+ }
+ SkASSERT(firstT >= 0);
+ // sort the edges to find the leftmost
+ int step = 1;
+ int end = nextSpan(firstT, step);
+ if (end == -1) {
+ step = -1;
+ end = nextSpan(firstT, step);
+ SkASSERT(end != -1);
+ }
+ // if the topmost T is not on end, or is three-way or more, find left
+ // look for left-ness from tLeft to firstT (matching y of other)
+ SkTDArray<Angle> angles;
+ SkASSERT(firstT - end != 0);
+ addTwoAngles(end, firstT, angles);
+ buildAngles(firstT, angles, true);
+ SkTDArray<Angle*> sorted;
+ bool sortable = SortAngles(angles, sorted);
+ #if DEBUG_SORT
+ sorted[0]->segment()->debugShowSort(__FUNCTION__, sorted, 0, 0, 0);
+ #endif
+ if (onlySortable && !sortable) {
+ unsortable = true;
+ return NULL;
+ }
+ // skip edges that have already been processed
+ firstT = -1;
+ Segment* leftSegment;
+ do {
+ const Angle* angle = sorted[++firstT];
+ SkASSERT(!onlySortable || !angle->unsortable());
+ leftSegment = angle->segment();
+ tIndex = angle->end();
+ endIndex = angle->start();
+ } while (leftSegment->fTs[SkMin32(tIndex, endIndex)].fDone);
+ SkASSERT(!leftSegment->fTs[SkMin32(tIndex, endIndex)].fTiny);
+ return leftSegment;
+ }
+
+ // FIXME: not crazy about this
+ // when the intersections are performed, the other index is into an
+ // incomplete array. as the array grows, the indices become incorrect
+ // while the following fixes the indices up again, it isn't smart about
+ // skipping segments whose indices are already correct
+ // assuming we leave the code that wrote the index in the first place
+ void fixOtherTIndex() {
+ int iCount = fTs.count();
+ for (int i = 0; i < iCount; ++i) {
+ Span& iSpan = fTs[i];
+ double oT = iSpan.fOtherT;
+ Segment* other = iSpan.fOther;
+ int oCount = other->fTs.count();
+ for (int o = 0; o < oCount; ++o) {
+ Span& oSpan = other->fTs[o];
+ if (oT == oSpan.fT && this == oSpan.fOther) {
+ iSpan.fOtherIndex = o;
+ break;
+ }
+ }
+ }
+ }
+
+ void init(const SkPoint pts[], SkPath::Verb verb, bool operand, bool evenOdd) {
+ fDoneSpans = 0;
+ fOperand = operand;
+ fXor = evenOdd;
+ fPts = pts;
+ fVerb = verb;
+ }
+
+ void initWinding(int start, int end, int winding, int oppWinding) {
+ int local = spanSign(start, end);
+ if (local * winding >= 0) {
+ winding += local;
+ }
+ local = oppSign(start, end);
+ if (local * oppWinding >= 0) {
+ oppWinding += local;
+ }
+ (void) markAndChaseWinding(start, end, winding, oppWinding);
+ }
+
+ bool intersected() const {
+ return fTs.count() > 0;
+ }
+
+ bool isConnected(int startIndex, int endIndex) const {
+ return fTs[startIndex].fWindSum != SK_MinS32
+ || fTs[endIndex].fWindSum != SK_MinS32;
+ }
+
+ bool isHorizontal() const {
+ return fBounds.fTop == fBounds.fBottom;
+ }
+
+ bool isLinear(int start, int end) const {
+ if (fVerb == SkPath::kLine_Verb) {
+ return true;
+ }
+ if (fVerb == SkPath::kQuad_Verb) {
+ SkPoint qPart[3];
+ QuadSubDivide(fPts, fTs[start].fT, fTs[end].fT, qPart);
+ return QuadIsLinear(qPart);
+ } else {
+ SkASSERT(fVerb == SkPath::kCubic_Verb);
+ SkPoint cPart[4];
+ CubicSubDivide(fPts, fTs[start].fT, fTs[end].fT, cPart);
+ return CubicIsLinear(cPart);
+ }
+ }
+
+ // OPTIMIZE: successive calls could start were the last leaves off
+ // or calls could specialize to walk forwards or backwards
+ bool isMissing(double startT) const {
+ size_t tCount = fTs.count();
+ for (size_t index = 0; index < tCount; ++index) {
+ if (approximately_zero(startT - fTs[index].fT)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ bool isSimple(int end) const {
+ int count = fTs.count();
+ if (count == 2) {
+ return true;
+ }
+ double t = fTs[end].fT;
+ if (approximately_less_than_zero(t)) {
+ return !approximately_less_than_zero(fTs[1].fT);
+ }
+ if (approximately_greater_than_one(t)) {
+ return !approximately_greater_than_one(fTs[count - 2].fT);
+ }
+ return false;
+ }
+
+ bool isVertical() const {
+ return fBounds.fLeft == fBounds.fRight;
+ }
+
+ SkScalar leftMost(int start, int end) const {
+ return (*SegmentLeftMost[fVerb])(fPts, fTs[start].fT, fTs[end].fT);
+ }
+
+ // this span is excluded by the winding rule -- chase the ends
+ // as long as they are unambiguous to mark connections as done
+ // and give them the same winding value
+ Span* markAndChaseDone(const Angle* angle, int winding) {
+ int index = angle->start();
+ int endIndex = angle->end();
+ return markAndChaseDone(index, endIndex, winding);
+ }
+
+ Span* markAndChaseDone(int index, int endIndex, int winding) {
+ int step = SkSign32(endIndex - index);
+ int min = SkMin32(index, endIndex);
+ markDone(min, winding);
+ Span* last;
+ Segment* other = this;
+ while ((other = other->nextChase(index, step, min, last))) {
+ other->markDone(min, winding);
+ }
+ return last;
+ }
+
+ Span* markAndChaseDoneBinary(const Angle* angle, int winding, int oppWinding) {
+ int index = angle->start();
+ int endIndex = angle->end();
+ int step = SkSign32(endIndex - index);
+ int min = SkMin32(index, endIndex);
+ markDoneBinary(min, winding, oppWinding);
+ Span* last;
+ Segment* other = this;
+ while ((other = other->nextChase(index, step, min, last))) {
+ other->markDoneBinary(min, winding, oppWinding);
+ }
+ return last;
+ }
+
+ Span* markAndChaseDoneBinary(int index, int endIndex) {
+ int step = SkSign32(endIndex - index);
+ int min = SkMin32(index, endIndex);
+ markDoneBinary(min);
+ Span* last;
+ Segment* other = this;
+ while ((other = other->nextChase(index, step, min, last))) {
+ if (other->done()) {
+ return NULL;
+ }
+ other->markDoneBinary(min);
+ }
+ return last;
+ }
+
+ Span* markAndChaseDoneUnary(const Angle* angle, int winding) {
+ int index = angle->start();
+ int endIndex = angle->end();
+ return markAndChaseDone(index, endIndex, winding);
+ }
+
+ Span* markAndChaseWinding(const Angle* angle, const int winding) {
+ int index = angle->start();
+ int endIndex = angle->end();
+ int step = SkSign32(endIndex - index);
+ int min = SkMin32(index, endIndex);
+ markWinding(min, winding);
+ Span* last;
+ Segment* other = this;
+ while ((other = other->nextChase(index, step, min, last))) {
+ if (other->fTs[min].fWindSum != SK_MinS32) {
+ SkASSERT(other->fTs[min].fWindSum == winding);
+ return NULL;
+ }
+ other->markWinding(min, winding);
+ }
+ return last;
+ }
+
+ Span* markAndChaseWinding(int index, int endIndex, int winding, int oppWinding) {
+ int min = SkMin32(index, endIndex);
+ int step = SkSign32(endIndex - index);
+ markWinding(min, winding, oppWinding);
+ Span* last;
+ Segment* other = this;
+ while ((other = other->nextChase(index, step, min, last))) {
+ if (other->fTs[min].fWindSum != SK_MinS32) {
+ SkASSERT(other->fTs[min].fWindSum == winding);
+ return NULL;
+ }
+ other->markWinding(min, winding, oppWinding);
+ }
+ return last;
+ }
+
+ Span* markAndChaseWinding(const Angle* angle, int winding, int oppWinding) {
+ int start = angle->start();
+ int end = angle->end();
+ return markAndChaseWinding(start, end, winding, oppWinding);
+ }
+
+ Span* markAngle(int maxWinding, int sumWinding, bool activeAngle, const Angle* angle) {
+ SkASSERT(angle->segment() == this);
+ if (useInnerWinding(maxWinding, sumWinding)) {
+ maxWinding = sumWinding;
+ }
+ Span* last;
+ if (activeAngle) {
+ last = markAndChaseWinding(angle, maxWinding);
+ } else {
+ last = markAndChaseDoneUnary(angle, maxWinding);
+ }
+ return last;
+ }
+
+ Span* markAngle(int maxWinding, int sumWinding, int oppMaxWinding, int oppSumWinding,
+ bool activeAngle, const Angle* angle) {
+ SkASSERT(angle->segment() == this);
+ if (useInnerWinding(maxWinding, sumWinding)) {
+ maxWinding = sumWinding;
+ }
+ if (oppMaxWinding != oppSumWinding && useInnerWinding(oppMaxWinding, oppSumWinding)) {
+ oppMaxWinding = oppSumWinding;
+ }
+ Span* last;
+ if (activeAngle) {
+ last = markAndChaseWinding(angle, maxWinding, oppMaxWinding);
+ } else {
+ last = markAndChaseDoneBinary(angle, maxWinding, oppMaxWinding);
+ }
+ return last;
+ }
+
+ // FIXME: this should also mark spans with equal (x,y)
+ // This may be called when the segment is already marked done. While this
+ // wastes time, it shouldn't do any more than spin through the T spans.
+ // OPTIMIZATION: abort on first done found (assuming that this code is
+ // always called to mark segments done).
+ void markDone(int index, int winding) {
+ // SkASSERT(!done());
+ SkASSERT(winding);
+ double referenceT = fTs[index].fT;
+ int lesser = index;
+ while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) {
+ markOneDone(__FUNCTION__, lesser, winding);
+ }
+ do {
+ markOneDone(__FUNCTION__, index, winding);
+ } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT));
+ }
+
+ void markDoneBinary(int index, int winding, int oppWinding) {
+ // SkASSERT(!done());
+ SkASSERT(winding || oppWinding);
+ double referenceT = fTs[index].fT;
+ int lesser = index;
+ while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) {
+ markOneDoneBinary(__FUNCTION__, lesser, winding, oppWinding);
+ }
+ do {
+ markOneDoneBinary(__FUNCTION__, index, winding, oppWinding);
+ } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT));
+ }
+
+ void markDoneBinary(int index) {
+ double referenceT = fTs[index].fT;
+ int lesser = index;
+ while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) {
+ markOneDoneBinary(__FUNCTION__, lesser);
+ }
+ do {
+ markOneDoneBinary(__FUNCTION__, index);
+ } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT));
+ }
+
+ void markDoneUnary(int index, int winding) {
+ // SkASSERT(!done());
+ SkASSERT(winding);
+ double referenceT = fTs[index].fT;
+ int lesser = index;
+ while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) {
+ markOneDoneUnary(__FUNCTION__, lesser, winding);
+ }
+ do {
+ markOneDoneUnary(__FUNCTION__, index, winding);
+ } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT));
+ }
+
+ void markDoneUnary(int index) {
+ double referenceT = fTs[index].fT;
+ int lesser = index;
+ while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) {
+ markOneDoneUnary(__FUNCTION__, lesser);
+ }
+ do {
+ markOneDoneUnary(__FUNCTION__, index);
+ } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT));
+ }
+
+ void markOneDone(const char* funName, int tIndex, int winding) {
+ Span* span = markOneWinding(funName, tIndex, winding);
+ if (!span) {
+ return;
+ }
+ span->fDone = true;
+ fDoneSpans++;
+ }
+
+ void markOneDoneBinary(const char* funName, int tIndex) {
+ Span* span = verifyOneWinding(funName, tIndex);
+ if (!span) {
+ return;
+ }
+ span->fDone = true;
+ fDoneSpans++;
+ }
+
+ void markOneDoneBinary(const char* funName, int tIndex, int winding, int oppWinding) {
+ Span* span = markOneWinding(funName, tIndex, winding, oppWinding);
+ if (!span) {
+ return;
+ }
+ span->fDone = true;
+ fDoneSpans++;
+ }
+
+ void markOneDoneUnary(const char* funName, int tIndex) {
+ Span* span = verifyOneWindingU(funName, tIndex);
+ if (!span) {
+ return;
+ }
+ span->fDone = true;
+ fDoneSpans++;
+ }
+
+ void markOneDoneUnary(const char* funName, int tIndex, int winding) {
+ Span* span = markOneWinding(funName, tIndex, winding);
+ if (!span) {
+ return;
+ }
+ span->fDone = true;
+ fDoneSpans++;
+ }
+
+ Span* markOneWinding(const char* funName, int tIndex, int winding) {
+ Span& span = fTs[tIndex];
+ if (span.fDone) {
+ return NULL;
+ }
+ #if DEBUG_MARK_DONE
+ debugShowNewWinding(funName, span, winding);
+ #endif
+ SkASSERT(span.fWindSum == SK_MinS32 || span.fWindSum == winding);
+ #ifdef SK_DEBUG
+ SkASSERT(abs(winding) <= gDebugMaxWindSum);
+ #endif
+ span.fWindSum = winding;
+ return &span;
+ }
+
+ Span* markOneWinding(const char* funName, int tIndex, int winding, int oppWinding) {
+ Span& span = fTs[tIndex];
+ if (span.fDone) {
+ return NULL;
+ }
+ #if DEBUG_MARK_DONE
+ debugShowNewWinding(funName, span, winding, oppWinding);
+ #endif
+ SkASSERT(span.fWindSum == SK_MinS32 || span.fWindSum == winding);
+ #ifdef SK_DEBUG
+ SkASSERT(abs(winding) <= gDebugMaxWindSum);
+ #endif
+ span.fWindSum = winding;
+ SkASSERT(span.fOppSum == SK_MinS32 || span.fOppSum == oppWinding);
+ #ifdef SK_DEBUG
+ SkASSERT(abs(oppWinding) <= gDebugMaxWindSum);
+ #endif
+ span.fOppSum = oppWinding;
+ return &span;
+ }
+
+ Span* verifyOneWinding(const char* funName, int tIndex) {
+ Span& span = fTs[tIndex];
+ if (span.fDone) {
+ return NULL;
+ }
+ #if DEBUG_MARK_DONE
+ debugShowNewWinding(funName, span, span.fWindSum, span.fOppSum);
+ #endif
+ SkASSERT(span.fWindSum != SK_MinS32);
+ SkASSERT(span.fOppSum != SK_MinS32);
+ return &span;
+ }
+
+ Span* verifyOneWindingU(const char* funName, int tIndex) {
+ Span& span = fTs[tIndex];
+ if (span.fDone) {
+ return NULL;
+ }
+ #if DEBUG_MARK_DONE
+ debugShowNewWinding(funName, span, span.fWindSum);
+ #endif
+ SkASSERT(span.fWindSum != SK_MinS32);
+ return &span;
+ }
+
+ // note that just because a span has one end that is unsortable, that's
+ // not enough to mark it done. The other end may be sortable, allowing the
+ // span to be added.
+ void markUnsortable(int start, int end) {
+ Span* span = &fTs[start];
+ if (start < end) {
+ span->fUnsortableStart = true;
+ } else {
+ --span;
+ span->fUnsortableEnd = true;
+ }
+ if (!span->fUnsortableStart || !span->fUnsortableEnd || span->fDone) {
+ return;
+ }
+ span->fDone = true;
+ fDoneSpans++;
+ }
+
+ void markWinding(int index, int winding) {
+ // SkASSERT(!done());
+ SkASSERT(winding);
+ double referenceT = fTs[index].fT;
+ int lesser = index;
+ while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) {
+ markOneWinding(__FUNCTION__, lesser, winding);
+ }
+ do {
+ markOneWinding(__FUNCTION__, index, winding);
+ } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT));
+ }
+
+ void markWinding(int index, int winding, int oppWinding) {
+ // SkASSERT(!done());
+ SkASSERT(winding || oppWinding);
+ double referenceT = fTs[index].fT;
+ int lesser = index;
+ while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) {
+ markOneWinding(__FUNCTION__, lesser, winding, oppWinding);
+ }
+ do {
+ markOneWinding(__FUNCTION__, index, winding, oppWinding);
+ } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT));
+ }
+
+ void matchWindingValue(int tIndex, double t, bool borrowWind) {
+ int nextDoorWind = SK_MaxS32;
+ int nextOppWind = SK_MaxS32;
+ if (tIndex > 0) {
+ const Span& below = fTs[tIndex - 1];
+ if (approximately_negative(t - below.fT)) {
+ nextDoorWind = below.fWindValue;
+ nextOppWind = below.fOppValue;
+ }
+ }
+ if (nextDoorWind == SK_MaxS32 && tIndex + 1 < fTs.count()) {
+ const Span& above = fTs[tIndex + 1];
+ if (approximately_negative(above.fT - t)) {
+ nextDoorWind = above.fWindValue;
+ nextOppWind = above.fOppValue;
+ }
+ }
+ if (nextDoorWind == SK_MaxS32 && borrowWind && tIndex > 0 && t < 1) {
+ const Span& below = fTs[tIndex - 1];
+ nextDoorWind = below.fWindValue;
+ nextOppWind = below.fOppValue;
+ }
+ if (nextDoorWind != SK_MaxS32) {
+ Span& newSpan = fTs[tIndex];
+ newSpan.fWindValue = nextDoorWind;
+ newSpan.fOppValue = nextOppWind;
+ if (!nextDoorWind && !nextOppWind && !newSpan.fDone) {
+ newSpan.fDone = true;
+ ++fDoneSpans;
+ }
+ }
+ }
+
+ // return span if when chasing, two or more radiating spans are not done
+ // OPTIMIZATION: ? multiple spans is detected when there is only one valid
+ // candidate and the remaining spans have windValue == 0 (canceled by
+ // coincidence). The coincident edges could either be removed altogether,
+ // or this code could be more complicated in detecting this case. Worth it?
+ bool multipleSpans(int end) const {
+ return end > 0 && end < fTs.count() - 1;
+ }
+
+ Segment* nextChase(int& index, const int step, int& min, Span*& last) const {
+ int end = nextExactSpan(index, step);
+ SkASSERT(end >= 0);
+ if (multipleSpans(end)) {
+ last = &fTs[end];
+ return NULL;
+ }
+ const Span& endSpan = fTs[end];
+ Segment* other = endSpan.fOther;
+ index = endSpan.fOtherIndex;
+ int otherEnd = other->nextExactSpan(index, step);
+ min = SkMin32(index, otherEnd);
+ return other;
+ }
+
+ // This has callers for two different situations: one establishes the end
+ // of the current span, and one establishes the beginning of the next span
+ // (thus the name). When this is looking for the end of the current span,
+ // coincidence is found when the beginning Ts contain -step and the end
+ // contains step. When it is looking for the beginning of the next, the
+ // first Ts found can be ignored and the last Ts should contain -step.
+ // OPTIMIZATION: probably should split into two functions
+ int nextSpan(int from, int step) const {
+ const Span& fromSpan = fTs[from];
+ int count = fTs.count();
+ int to = from;
+ while (step > 0 ? ++to < count : --to >= 0) {
+ const Span& span = fTs[to];
+ if (approximately_zero(span.fT - fromSpan.fT)) {
+ continue;
+ }
+ return to;
+ }
+ return -1;
+ }
+
+ // FIXME
+ // this returns at any difference in T, vs. a preset minimum. It may be
+ // that all callers to nextSpan should use this instead.
+ // OPTIMIZATION splitting this into separate loops for up/down steps
+ // would allow using precisely_negative instead of precisely_zero
+ int nextExactSpan(int from, int step) const {
+ const Span& fromSpan = fTs[from];
+ int count = fTs.count();
+ int to = from;
+ while (step > 0 ? ++to < count : --to >= 0) {
+ const Span& span = fTs[to];
+ if (precisely_zero(span.fT - fromSpan.fT)) {
+ continue;
+ }
+ return to;
+ }
+ return -1;
+ }
+
+ bool operand() const {
+ return fOperand;
+ }
+
+ int oppSign(const Angle* angle) const {
+ SkASSERT(angle->segment() == this);
+ return oppSign(angle->start(), angle->end());
+ }
+
+ int oppSign(int startIndex, int endIndex) const {
+ int result = startIndex < endIndex ? -fTs[startIndex].fOppValue
+ : fTs[endIndex].fOppValue;
+#if DEBUG_WIND_BUMP
+ SkDebugf("%s oppSign=%d\n", __FUNCTION__, result);
+#endif
+ return result;
+ }
+
+ int oppSum(int tIndex) const {
+ return fTs[tIndex].fOppSum;
+ }
+
+ int oppSum(const Angle* angle) const {
+ int lesser = SkMin32(angle->start(), angle->end());
+ return fTs[lesser].fOppSum;
+ }
+
+ int oppValue(int tIndex) const {
+ return fTs[tIndex].fOppValue;
+ }
+
+ int oppValue(const Angle* angle) const {
+ int lesser = SkMin32(angle->start(), angle->end());
+ return fTs[lesser].fOppValue;
+ }
+
+ const SkPoint* pts() const {
+ return fPts;
+ }
+
+ void reset() {
+ init(NULL, (SkPath::Verb) -1, false, false);
+ fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax);
+ fTs.reset();
+ }
+
+ void setOppXor(bool isOppXor) {
+ fOppXor = isOppXor;
+ }
+
+ void setUpWinding(int index, int endIndex, int& maxWinding, int& sumWinding) {
+ int deltaSum = spanSign(index, endIndex);
+ maxWinding = sumWinding;
+ sumWinding = sumWinding -= deltaSum;
+ }
+
+ void setUpWindings(int index, int endIndex, int& sumMiWinding, int& sumSuWinding,
+ int& maxWinding, int& sumWinding, int& oppMaxWinding, int& oppSumWinding) {
+ int deltaSum = spanSign(index, endIndex);
+ int oppDeltaSum = oppSign(index, endIndex);
+ if (operand()) {
+ maxWinding = sumSuWinding;
+ sumWinding = sumSuWinding -= deltaSum;
+ oppMaxWinding = sumMiWinding;
+ oppSumWinding = sumMiWinding -= oppDeltaSum;
+ } else {
+ maxWinding = sumMiWinding;
+ sumWinding = sumMiWinding -= deltaSum;
+ oppMaxWinding = sumSuWinding;
+ oppSumWinding = sumSuWinding -= oppDeltaSum;
+ }
+ }
+
+ // This marks all spans unsortable so that this info is available for early
+ // exclusion in find top and others. This could be optimized to only mark
+ // adjacent spans that unsortable. However, this makes it difficult to later
+ // determine starting points for edge detection in find top and the like.
+ static bool SortAngles(SkTDArray<Angle>& angles, SkTDArray<Angle*>& angleList) {
+ bool sortable = true;
+ int angleCount = angles.count();
+ int angleIndex;
+ angleList.setReserve(angleCount);
+ for (angleIndex = 0; angleIndex < angleCount; ++angleIndex) {
+ Angle& angle = angles[angleIndex];
+ *angleList.append() = &angle;
+ sortable &= !angle.unsortable();
+ }
+ if (sortable) {
+ QSort<Angle>(angleList.begin(), angleList.end() - 1);
+ for (angleIndex = 0; angleIndex < angleCount; ++angleIndex) {
+ if (angles[angleIndex].unsortable()) {
+ sortable = false;
+ break;
+ }
+ }
+ }
+ if (!sortable) {
+ for (angleIndex = 0; angleIndex < angleCount; ++angleIndex) {
+ Angle& angle = angles[angleIndex];
+ angle.segment()->markUnsortable(angle.start(), angle.end());
+ }
+ }
+ return sortable;
+ }
+
+ // OPTIMIZATION: mark as debugging only if used solely by tests
+ const Span& span(int tIndex) const {
+ return fTs[tIndex];
+ }
+
+ int spanSign(const Angle* angle) const {
+ SkASSERT(angle->segment() == this);
+ return spanSign(angle->start(), angle->end());
+ }
+
+ int spanSign(int startIndex, int endIndex) const {
+ int result = startIndex < endIndex ? -fTs[startIndex].fWindValue
+ : fTs[endIndex].fWindValue;
+#if DEBUG_WIND_BUMP
+ SkDebugf("%s spanSign=%d\n", __FUNCTION__, result);
+#endif
+ return result;
+ }
+
+ // OPTIMIZATION: mark as debugging only if used solely by tests
+ double t(int tIndex) const {
+ return fTs[tIndex].fT;
+ }
+
+ bool tiny(const Angle* angle) const {
+ int start = angle->start();
+ int end = angle->end();
+ const Span& mSpan = fTs[SkMin32(start, end)];
+ return mSpan.fTiny;
+ }
+
+ static void TrackOutside(SkTDArray<double>& outsideTs, double end,
+ double start) {
+ int outCount = outsideTs.count();
+ if (outCount == 0 || !approximately_negative(end - outsideTs[outCount - 2])) {
+ *outsideTs.append() = end;
+ *outsideTs.append() = start;
+ }
+ }
+
+ void undoneSpan(int& start, int& end) {
+ size_t tCount = fTs.count();
+ size_t index;
+ for (index = 0; index < tCount; ++index) {
+ if (!fTs[index].fDone) {
+ break;
+ }
+ }
+ SkASSERT(index < tCount - 1);
+ start = index;
+ double startT = fTs[index].fT;
+ while (approximately_negative(fTs[++index].fT - startT))
+ SkASSERT(index < tCount);
+ SkASSERT(index < tCount);
+ end = index;
+ }
+
+ bool unsortable(int index) const {
+ return fTs[index].fUnsortableStart || fTs[index].fUnsortableEnd;
+ }
+
+ void updatePts(const SkPoint pts[]) {
+ fPts = pts;
+ }
+
+ int updateOppWinding(int index, int endIndex) const {
+ int lesser = SkMin32(index, endIndex);
+ int oppWinding = oppSum(lesser);
+ int oppSpanWinding = oppSign(index, endIndex);
+ if (oppSpanWinding && useInnerWinding(oppWinding - oppSpanWinding, oppWinding)) {
+ oppWinding -= oppSpanWinding;
+ }
+ return oppWinding;
+ }
+
+ int updateOppWinding(const Angle* angle) const {
+ int startIndex = angle->start();
+ int endIndex = angle->end();
+ return updateOppWinding(endIndex, startIndex);
+ }
+
+ int updateOppWindingReverse(const Angle* angle) const {
+ int startIndex = angle->start();
+ int endIndex = angle->end();
+ return updateOppWinding(startIndex, endIndex);
+ }
+
+ int updateWinding(int index, int endIndex) const {
+ int lesser = SkMin32(index, endIndex);
+ int winding = windSum(lesser);
+ int spanWinding = spanSign(index, endIndex);
+ if (winding && useInnerWinding(winding - spanWinding, winding)) {
+ winding -= spanWinding;
+ }
+ return winding;
+ }
+
+ int updateWinding(const Angle* angle) const {
+ int startIndex = angle->start();
+ int endIndex = angle->end();
+ return updateWinding(endIndex, startIndex);
+ }
+
+ int updateWindingReverse(const Angle* angle) const {
+ int startIndex = angle->start();
+ int endIndex = angle->end();
+ return updateWinding(startIndex, endIndex);
+ }
+
+ SkPath::Verb verb() const {
+ return fVerb;
+ }
+
+ int windingAtT(double tHit, int tIndex, bool crossOpp) const {
+ if (approximately_zero(tHit - t(tIndex))) { // if we hit the end of a span, disregard
+ return SK_MinS32;
+ }
+ int winding = crossOpp ? oppSum(tIndex) : windSum(tIndex);
+ SkASSERT(winding != SK_MinS32);
+ int windVal = crossOpp ? oppValue(tIndex) : windValue(tIndex);
+ #if DEBUG_WINDING
+ SkDebugf("%s single winding=%d windValue=%d\n", __FUNCTION__, winding,
+ windVal);
+ #endif
+ // see if a + change in T results in a +/- change in X (compute x'(T))
+ SkScalar dx = (*SegmentDXAtT[fVerb])(fPts, tHit);
+ if (fVerb > SkPath::kLine_Verb && approximately_zero(dx)) {
+ dx = fPts[2].fX - fPts[1].fX - dx;
+ }
+ #if DEBUG_WINDING
+ SkDebugf("%s dx=%1.9g\n", __FUNCTION__, dx);
+ #endif
+ SkASSERT(dx != 0);
+ if (winding * dx > 0) { // if same signs, result is negative
+ winding += dx > 0 ? -windVal : windVal;
+ #if DEBUG_WINDING
+ SkDebugf("%s final winding=%d\n", __FUNCTION__, winding);
+ #endif
+ }
+ return winding;
+ }
+
+ int windSum(int tIndex) const {
+ return fTs[tIndex].fWindSum;
+ }
+
+ int windSum(const Angle* angle) const {
+ int start = angle->start();
+ int end = angle->end();
+ int index = SkMin32(start, end);
+ return windSum(index);
+ }
+
+ int windValue(int tIndex) const {
+ return fTs[tIndex].fWindValue;
+ }
+
+ int windValue(const Angle* angle) const {
+ int start = angle->start();
+ int end = angle->end();
+ int index = SkMin32(start, end);
+ return windValue(index);
+ }
+
+ SkScalar xAtT(const Span* span) const {
+ return xyAtT(span).fX;
+ }
+
+ const SkPoint& xyAtT(int index) const {
+ return xyAtT(&fTs[index]);
+ }
+
+ const SkPoint& xyAtT(const Span* span) const {
+ if (SkScalarIsNaN(span->fPt.fX)) {
+ if (span->fT == 0) {
+ span->fPt = fPts[0];
+ } else if (span->fT == 1) {
+ span->fPt = fPts[fVerb];
+ } else {
+ (*SegmentXYAtT[fVerb])(fPts, span->fT, &span->fPt);
+ }
+ }
+ return span->fPt;
+ }
+
+ SkScalar yAtT(int index) const {
+ return yAtT(&fTs[index]);
+ }
+
+ SkScalar yAtT(const Span* span) const {
+ return xyAtT(span).fY;
+ }
+
+ void zeroCoincidentOpp(Span* oTest, int index) {
+ Span* const test = &fTs[index];
+ Span* end = test;
+ do {
+ end->fOppValue = 0;
+ end = &fTs[++index];
+ } while (approximately_negative(end->fT - test->fT));
+ }
+
+ void zeroCoincidentOther(Span* test, const double tRatio, const double oEndT, int oIndex) {
+ Span* const oTest = &fTs[oIndex];
+ Span* oEnd = oTest;
+ const double startT = test->fT;
+ const double oStartT = oTest->fT;
+ double otherTMatch = (test->fT - startT) * tRatio + oStartT;
+ while (!approximately_negative(oEndT - oEnd->fT)
+ && approximately_negative(oEnd->fT - otherTMatch)) {
+ oEnd->fOppValue = 0;
+ oEnd = &fTs[++oIndex];
+ }
+ }
+
+ void zeroSpan(Span* span) {
+ SkASSERT(span->fWindValue > 0 || span->fOppValue > 0);
+ span->fWindValue = 0;
+ span->fOppValue = 0;
+ SkASSERT(!span->fDone);
+ span->fDone = true;
+ ++fDoneSpans;
+ }
+
+#if DEBUG_DUMP
+ void dump() const {
+ const char className[] = "Segment";
+ const int tab = 4;
+ for (int i = 0; i < fTs.count(); ++i) {
+ SkPoint out;
+ (*SegmentXYAtT[fVerb])(fPts, t(i), &out);
+ SkDebugf("%*s [%d] %s.fTs[%d]=%1.9g (%1.9g,%1.9g) other=%d"
+ " otherT=%1.9g windSum=%d\n",
+ tab + sizeof(className), className, fID,
+ kLVerbStr[fVerb], i, fTs[i].fT, out.fX, out.fY,
+ fTs[i].fOther->fID, fTs[i].fOtherT, fTs[i].fWindSum);
+ }
+ SkDebugf("%*s [%d] fBounds=(l:%1.9g, t:%1.9g r:%1.9g, b:%1.9g)",
+ tab + sizeof(className), className, fID,
+ fBounds.fLeft, fBounds.fTop, fBounds.fRight, fBounds.fBottom);
+ }
+#endif
+
+#if DEBUG_CONCIDENT
+ // assert if pair has not already been added
+ void debugAddTPair(double t, const Segment& other, double otherT) const {
+ for (int i = 0; i < fTs.count(); ++i) {
+ if (fTs[i].fT == t && fTs[i].fOther == &other && fTs[i].fOtherT == otherT) {
+ return;
+ }
+ }
+ SkASSERT(0);
+ }
+#endif
+
+#if DEBUG_DUMP
+ int debugID() const {
+ return fID;
+ }
+#endif
+
+#if DEBUG_WINDING
+ void debugShowSums() const {
+ SkDebugf("%s id=%d (%1.9g,%1.9g %1.9g,%1.9g)", __FUNCTION__, fID,
+ fPts[0].fX, fPts[0].fY, fPts[fVerb].fX, fPts[fVerb].fY);
+ for (int i = 0; i < fTs.count(); ++i) {
+ const Span& span = fTs[i];
+ SkDebugf(" [t=%1.3g %1.9g,%1.9g w=", span.fT, xAtT(&span), yAtT(&span));
+ if (span.fWindSum == SK_MinS32) {
+ SkDebugf("?");
+ } else {
+ SkDebugf("%d", span.fWindSum);
+ }
+ SkDebugf("]");
+ }
+ SkDebugf("\n");
+ }
+#endif
+
+#if DEBUG_CONCIDENT
+ void debugShowTs() const {
+ SkDebugf("%s id=%d", __FUNCTION__, fID);
+ int lastWind = -1;
+ int lastOpp = -1;
+ double lastT = -1;
+ int i;
+ for (i = 0; i < fTs.count(); ++i) {
+ bool change = lastT != fTs[i].fT || lastWind != fTs[i].fWindValue
+ || lastOpp != fTs[i].fOppValue;
+ if (change && lastWind >= 0) {
+ SkDebugf(" t=%1.3g %1.9g,%1.9g w=%d o=%d]",
+ lastT, xyAtT(i - 1).fX, xyAtT(i - 1).fY, lastWind, lastOpp);
+ }
+ if (change) {
+ SkDebugf(" [o=%d", fTs[i].fOther->fID);
+ lastWind = fTs[i].fWindValue;
+ lastOpp = fTs[i].fOppValue;
+ lastT = fTs[i].fT;
+ } else {
+ SkDebugf(",%d", fTs[i].fOther->fID);
+ }
+ }
+ if (i <= 0) {
+ return;
+ }
+ SkDebugf(" t=%1.3g %1.9g,%1.9g w=%d o=%d]",
+ lastT, xyAtT(i - 1).fX, xyAtT(i - 1).fY, lastWind, lastOpp);
+ if (fOperand) {
+ SkDebugf(" operand");
+ }
+ if (done()) {
+ SkDebugf(" done");
+ }
+ SkDebugf("\n");
+ }
+#endif
+
+#if DEBUG_ACTIVE_SPANS
+ void debugShowActiveSpans() const {
+ if (done()) {
+ return;
+ }
+#if DEBUG_ACTIVE_SPANS_SHORT_FORM
+ int lastId = -1;
+ double lastT = -1;
+#endif
+ for (int i = 0; i < fTs.count(); ++i) {
+ if (fTs[i].fDone) {
+ continue;
+ }
+#if DEBUG_ACTIVE_SPANS_SHORT_FORM
+ if (lastId == fID && lastT == fTs[i].fT) {
+ continue;
+ }
+ lastId = fID;
+ lastT = fTs[i].fT;
+#endif
+ SkDebugf("%s id=%d", __FUNCTION__, fID);
+ SkDebugf(" (%1.9g,%1.9g", fPts[0].fX, fPts[0].fY);
+ for (int vIndex = 1; vIndex <= fVerb; ++vIndex) {
+ SkDebugf(" %1.9g,%1.9g", fPts[vIndex].fX, fPts[vIndex].fY);
+ }
+ const Span* span = &fTs[i];
+ SkDebugf(") t=%1.9g (%1.9g,%1.9g)", fTs[i].fT,
+ xAtT(span), yAtT(span));
+ const Segment* other = fTs[i].fOther;
+ SkDebugf(" other=%d otherT=%1.9g otherIndex=%d windSum=",
+ other->fID, fTs[i].fOtherT, fTs[i].fOtherIndex);
+ if (fTs[i].fWindSum == SK_MinS32) {
+ SkDebugf("?");
+ } else {
+ SkDebugf("%d", fTs[i].fWindSum);
+ }
+ SkDebugf(" windValue=%d oppValue=%d\n", fTs[i].fWindValue, fTs[i].fOppValue);
+ }
+ }
+
+ // This isn't useful yet -- but leaving it in for now in case i think of something
+ // to use it for
+ void validateActiveSpans() const {
+ if (done()) {
+ return;
+ }
+ int tCount = fTs.count();
+ for (int index = 0; index < tCount; ++index) {
+ if (fTs[index].fDone) {
+ continue;
+ }
+ // count number of connections which are not done
+ int first = index;
+ double baseT = fTs[index].fT;
+ while (first > 0 && approximately_equal(fTs[first - 1].fT, baseT)) {
+ --first;
+ }
+ int last = index;
+ while (last < tCount - 1 && approximately_equal(fTs[last + 1].fT, baseT)) {
+ ++last;
+ }
+ int connections = 0;
+ connections += first > 0 && !fTs[first - 1].fDone;
+ for (int test = first; test <= last; ++test) {
+ connections += !fTs[test].fDone;
+ const Segment* other = fTs[test].fOther;
+ int oIndex = fTs[test].fOtherIndex;
+ connections += !other->fTs[oIndex].fDone;
+ connections += oIndex > 0 && !other->fTs[oIndex - 1].fDone;
+ }
+ // SkASSERT(!(connections & 1));
+ }
+ }
+#endif
+
+#if DEBUG_MARK_DONE
+ void debugShowNewWinding(const char* fun, const Span& span, int winding) {
+ const SkPoint& pt = xyAtT(&span);
+ SkDebugf("%s id=%d", fun, fID);
+ SkDebugf(" (%1.9g,%1.9g", fPts[0].fX, fPts[0].fY);
+ for (int vIndex = 1; vIndex <= fVerb; ++vIndex) {
+ SkDebugf(" %1.9g,%1.9g", fPts[vIndex].fX, fPts[vIndex].fY);
+ }
+ SkASSERT(&span == &span.fOther->fTs[span.fOtherIndex].fOther->
+ fTs[span.fOther->fTs[span.fOtherIndex].fOtherIndex]);
+ SkDebugf(") t=%1.9g [%d] (%1.9g,%1.9g) newWindSum=%d windSum=",
+ span.fT, span.fOther->fTs[span.fOtherIndex].fOtherIndex, pt.fX, pt.fY, winding);
+ if (span.fWindSum == SK_MinS32) {
+ SkDebugf("?");
+ } else {
+ SkDebugf("%d", span.fWindSum);
+ }
+ SkDebugf(" windValue=%d\n", span.fWindValue);
+ }
+
+ void debugShowNewWinding(const char* fun, const Span& span, int winding, int oppWinding) {
+ const SkPoint& pt = xyAtT(&span);
+ SkDebugf("%s id=%d", fun, fID);
+ SkDebugf(" (%1.9g,%1.9g", fPts[0].fX, fPts[0].fY);
+ for (int vIndex = 1; vIndex <= fVerb; ++vIndex) {
+ SkDebugf(" %1.9g,%1.9g", fPts[vIndex].fX, fPts[vIndex].fY);
+ }
+ SkASSERT(&span == &span.fOther->fTs[span.fOtherIndex].fOther->
+ fTs[span.fOther->fTs[span.fOtherIndex].fOtherIndex]);
+ SkDebugf(") t=%1.9g [%d] (%1.9g,%1.9g) newWindSum=%d newOppSum=%d oppSum=",
+ span.fT, span.fOther->fTs[span.fOtherIndex].fOtherIndex, pt.fX, pt.fY,
+ winding, oppWinding);
+ if (span.fOppSum == SK_MinS32) {
+ SkDebugf("?");
+ } else {
+ SkDebugf("%d", span.fOppSum);
+ }
+ SkDebugf(" windSum=");
+ if (span.fWindSum == SK_MinS32) {
+ SkDebugf("?");
+ } else {
+ SkDebugf("%d", span.fWindSum);
+ }
+ SkDebugf(" windValue=%d\n", span.fWindValue);
+ }
+#endif
+
+#if DEBUG_SORT
+ void debugShowSort(const char* fun, const SkTDArray<Angle*>& angles, int first,
+ const int contourWinding, const int oppContourWinding) const {
+ SkASSERT(angles[first]->segment() == this);
+ SkASSERT(angles.count() > 1);
+ int lastSum = contourWinding;
+ int oppLastSum = oppContourWinding;
+ const Angle* firstAngle = angles[first];
+ int windSum = lastSum - spanSign(firstAngle);
+ int oppoSign = oppSign(firstAngle);
+ int oppWindSum = oppLastSum - oppoSign;
+ SkDebugf("%s %s contourWinding=%d oppContourWinding=%d sign=%d\n", fun, __FUNCTION__,
+ contourWinding, oppContourWinding, spanSign(angles[first]));
+ int index = first;
+ bool firstTime = true;
+ do {
+ const Angle& angle = *angles[index];
+ const Segment& segment = *angle.segment();
+ int start = angle.start();
+ int end = angle.end();
+ const Span& sSpan = segment.fTs[start];
+ const Span& eSpan = segment.fTs[end];
+ const Span& mSpan = segment.fTs[SkMin32(start, end)];
+ bool opp = segment.fOperand ^ fOperand;
+ if (!firstTime) {
+ oppoSign = segment.oppSign(&angle);
+ if (opp) {
+ oppLastSum = oppWindSum;
+ oppWindSum -= segment.spanSign(&angle);
+ if (oppoSign) {
+ lastSum = windSum;
+ windSum -= oppoSign;
+ }
+ } else {
+ lastSum = windSum;
+ windSum -= segment.spanSign(&angle);
+ if (oppoSign) {
+ oppLastSum = oppWindSum;
+ oppWindSum -= oppoSign;
+ }
+ }
+ }
+ SkDebugf("%s [%d] %sid=%d %s start=%d (%1.9g,%,1.9g) end=%d (%1.9g,%,1.9g)"
+ " sign=%d windValue=%d windSum=",
+ __FUNCTION__, index, angle.unsortable() ? "*** UNSORTABLE *** " : "",
+ segment.fID, kLVerbStr[segment.fVerb],
+ start, segment.xAtT(&sSpan), segment.yAtT(&sSpan), end,
+ segment.xAtT(&eSpan), segment.yAtT(&eSpan), angle.sign(),
+ mSpan.fWindValue);
+ if (mSpan.fWindSum == SK_MinS32) {
+ SkDebugf("?");
+ } else {
+ SkDebugf("%d", mSpan.fWindSum);
+ }
+ int last, wind;
+ if (opp) {
+ last = oppLastSum;
+ wind = oppWindSum;
+ } else {
+ last = lastSum;
+ wind = windSum;
+ }
+ if (!oppoSign) {
+ SkDebugf(" %d->%d (max=%d)", last, wind,
+ useInnerWinding(last, wind) ? wind : last);
+ } else {
+ SkDebugf(" %d->%d (%d->%d)", last, wind, opp ? lastSum : oppLastSum,
+ opp ? windSum : oppWindSum);
+ }
+ SkDebugf(" done=%d tiny=%d opp=%d\n", mSpan.fDone, mSpan.fTiny, opp);
+#if false && DEBUG_ANGLE
+ angle.debugShow(segment.xyAtT(&sSpan));
+#endif
+ ++index;
+ if (index == angles.count()) {
+ index = 0;
+ }
+ if (firstTime) {
+ firstTime = false;
+ }
+ } while (index != first);
+ }
+
+ void debugShowSort(const char* fun, const SkTDArray<Angle*>& angles, int first) {
+ const Angle* firstAngle = angles[first];
+ const Segment* segment = firstAngle->segment();
+ int winding = segment->updateWinding(firstAngle);
+ int oppWinding = segment->updateOppWinding(firstAngle);
+ debugShowSort(fun, angles, first, winding, oppWinding);
+ }
+
+#endif
+
+#if DEBUG_WINDING
+ static char as_digit(int value) {
+ return value < 0 ? '?' : value <= 9 ? '0' + value : '+';
+ }
+#endif
+
+#if DEBUG_SHOW_WINDING
+ int debugShowWindingValues(int slotCount, int ofInterest) const {
+ if (!(1 << fID & ofInterest)) {
+ return 0;
+ }
+ int sum = 0;
+ SkTDArray<char> slots;
+ slots.setCount(slotCount * 2);
+ memset(slots.begin(), ' ', slotCount * 2);
+ for (int i = 0; i < fTs.count(); ++i) {
+ // if (!(1 << fTs[i].fOther->fID & ofInterest)) {
+ // continue;
+ // }
+ sum += fTs[i].fWindValue;
+ slots[fTs[i].fOther->fID - 1] = as_digit(fTs[i].fWindValue);
+ sum += fTs[i].fOppValue;
+ slots[slotCount + fTs[i].fOther->fID - 1] = as_digit(fTs[i].fOppValue);
+ }
+ SkDebugf("%s id=%2d %.*s | %.*s\n", __FUNCTION__, fID, slotCount, slots.begin(), slotCount,
+ slots.begin() + slotCount);
+ return sum;
+ }
+#endif
+
+private:
+ const SkPoint* fPts;
+ Bounds fBounds;
+ SkTDArray<Span> fTs; // two or more (always includes t=0 t=1)
+ // OPTIMIZATION: could pack donespans, verb, operand, xor into 1 int-sized value
+ int fDoneSpans; // quick check that segment is finished
+ // OPTIMIZATION: force the following to be byte-sized
+ SkPath::Verb fVerb;
+ bool fOperand;
+ bool fXor; // set if original contour had even-odd fill
+ bool fOppXor; // set if opposite operand had even-odd fill
+#if DEBUG_DUMP
+ int fID;
+#endif
+};
+
+class Contour;
+
+struct Coincidence {
+ Contour* fContours[2];
+ int fSegments[2];
+ double fTs[2][2];
+};
+
+class Contour {
+public:
+ Contour() {
+ reset();
+#if DEBUG_DUMP
+ fID = ++gContourID;
+#endif
+ }
+
+ bool operator<(const Contour& rh) const {
+ return fBounds.fTop == rh.fBounds.fTop
+ ? fBounds.fLeft < rh.fBounds.fLeft
+ : fBounds.fTop < rh.fBounds.fTop;
+ }
+
+ void addCoincident(int index, Contour* other, int otherIndex,
+ const Intersections& ts, bool swap) {
+ Coincidence& coincidence = *fCoincidences.append();
+ coincidence.fContours[0] = this; // FIXME: no need to store
+ coincidence.fContours[1] = other;
+ coincidence.fSegments[0] = index;
+ coincidence.fSegments[1] = otherIndex;
+ if (fSegments[index].verb() == SkPath::kLine_Verb &&
+ other->fSegments[otherIndex].verb() == SkPath::kLine_Verb) {
+ // FIXME: coincident lines use legacy Ts instead of coincident Ts
+ coincidence.fTs[swap][0] = ts.fT[0][0];
+ coincidence.fTs[swap][1] = ts.fT[0][1];
+ coincidence.fTs[!swap][0] = ts.fT[1][0];
+ coincidence.fTs[!swap][1] = ts.fT[1][1];
+ } else if (fSegments[index].verb() == SkPath::kQuad_Verb &&
+ other->fSegments[otherIndex].verb() == SkPath::kQuad_Verb) {
+ coincidence.fTs[swap][0] = ts.fCoincidentT[0][0];
+ coincidence.fTs[swap][1] = ts.fCoincidentT[0][1];
+ coincidence.fTs[!swap][0] = ts.fCoincidentT[1][0];
+ coincidence.fTs[!swap][1] = ts.fCoincidentT[1][1];
+ }
+ }
+
+ void addCross(const Contour* crosser) {
+#ifdef DEBUG_CROSS
+ for (int index = 0; index < fCrosses.count(); ++index) {
+ SkASSERT(fCrosses[index] != crosser);
+ }
+#endif
+ *fCrosses.append() = crosser;
+ }
+
+ void addCubic(const SkPoint pts[4]) {
+ fSegments.push_back().addCubic(pts, fOperand, fXor);
+ fContainsCurves = true;
+ }
+
+ int addLine(const SkPoint pts[2]) {
+ fSegments.push_back().addLine(pts, fOperand, fXor);
+ return fSegments.count();
+ }
+
+ void addOtherT(int segIndex, int tIndex, double otherT, int otherIndex) {
+ fSegments[segIndex].addOtherT(tIndex, otherT, otherIndex);
+ }
+
+ int addQuad(const SkPoint pts[3]) {
+ fSegments.push_back().addQuad(pts, fOperand, fXor);
+ fContainsCurves = true;
+ return fSegments.count();
+ }
+
+ int addT(int segIndex, double newT, Contour* other, int otherIndex) {
+ containsIntercepts();
+ return fSegments[segIndex].addT(newT, &other->fSegments[otherIndex]);
+ }
+
+ const Bounds& bounds() const {
+ return fBounds;
+ }
+
+ void complete() {
+ setBounds();
+ fContainsIntercepts = false;
+ }
+
+ void containsIntercepts() {
+ fContainsIntercepts = true;
+ }
+
+ const Segment* crossedSegmentX(const SkPoint& basePt, SkScalar& bestX,
+ int& tIndex, double& hitT, bool opp) {
+ int segmentCount = fSegments.count();
+ const Segment* bestSegment = NULL;
+ for (int test = 0; test < segmentCount; ++test) {
+ Segment* testSegment = &fSegments[test];
+ const SkRect& bounds = testSegment->bounds();
+ if (bounds.fRight <= bestX) {
+ continue;
+ }
+ if (bounds.fLeft >= basePt.fX) {
+ continue;
+ }
+ if (bounds.fTop > basePt.fY) {
+ continue;
+ }
+ if (bounds.fBottom < basePt.fY) {
+ continue;
+ }
+ if (bounds.fTop == bounds.fBottom) {
+ continue;
+ }
+ double testHitT;
+ int testT = testSegment->crossedSpanX(basePt, bestX, testHitT, opp);
+ if (testT >= 0) {
+ bestSegment = testSegment;
+ tIndex = testT;
+ hitT = testHitT;
+ }
+ }
+ return bestSegment;
+ }
+
+ const Segment* crossedSegmentY(const SkPoint& basePt, SkScalar& bestY,
+ int &tIndex, double& hitT, bool opp) {
+ int segmentCount = fSegments.count();
+ const Segment* bestSegment = NULL;
+ for (int test = 0; test < segmentCount; ++test) {
+ Segment* testSegment = &fSegments[test];
+ const SkRect& bounds = testSegment->bounds();
+ if (bounds.fBottom <= bestY) {
+ continue;
+ }
+ if (bounds.fTop >= basePt.fY) {
+ continue;
+ }
+ if (bounds.fLeft > basePt.fX) {
+ continue;
+ }
+ if (bounds.fRight < basePt.fX) {
+ continue;
+ }
+ if (bounds.fLeft == bounds.fRight) {
+ continue;
+ }
+ double testHitT;
+ int testT = testSegment->crossedSpanY(basePt, bestY, testHitT, opp);
+ if (testT >= 0) {
+ bestSegment = testSegment;
+ tIndex = testT;
+ hitT = testHitT;
+ }
+ }
+ return bestSegment;
+ }
+
+ bool crosses(const Contour* crosser) const {
+ for (int index = 0; index < fCrosses.count(); ++index) {
+ if (fCrosses[index] == crosser) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ const SkPoint& end() const {
+ const Segment& segment = fSegments.back();
+ return segment.pts()[segment.verb()];
+ }
+
+ void findTooCloseToCall() {
+ int segmentCount = fSegments.count();
+ for (int sIndex = 0; sIndex < segmentCount; ++sIndex) {
+ fSegments[sIndex].findTooCloseToCall();
+ }
+ }
+
+ void fixOtherTIndex() {
+ int segmentCount = fSegments.count();
+ for (int sIndex = 0; sIndex < segmentCount; ++sIndex) {
+ fSegments[sIndex].fixOtherTIndex();
+ }
+ }
+
+ bool operand() const {
+ return fOperand;
+ }
+
+ void reset() {
+ fSegments.reset();
+ fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax);
+ fContainsCurves = fContainsIntercepts = false;
+ }
+
+ void resolveCoincidence(SkTDArray<Contour*>& contourList) {
+ int count = fCoincidences.count();
+ for (int index = 0; index < count; ++index) {
+ Coincidence& coincidence = fCoincidences[index];
+ SkASSERT(coincidence.fContours[0] == this);
+ int thisIndex = coincidence.fSegments[0];
+ Segment& thisOne = fSegments[thisIndex];
+ Contour* otherContour = coincidence.fContours[1];
+ int otherIndex = coincidence.fSegments[1];
+ Segment& other = otherContour->fSegments[otherIndex];
+ if ((thisOne.done() || other.done()) && thisOne.complete() && other.complete()) {
+ continue;
+ }
+ #if DEBUG_CONCIDENT
+ thisOne.debugShowTs();
+ other.debugShowTs();
+ #endif
+ double startT = coincidence.fTs[0][0];
+ double endT = coincidence.fTs[0][1];
+ bool cancelers = false;
+ if (startT > endT) {
+ SkTSwap<double>(startT, endT);
+ cancelers ^= true; // FIXME: just assign true
+ }
+ SkASSERT(!approximately_negative(endT - startT));
+ double oStartT = coincidence.fTs[1][0];
+ double oEndT = coincidence.fTs[1][1];
+ if (oStartT > oEndT) {
+ SkTSwap<double>(oStartT, oEndT);
+ cancelers ^= true;
+ }
+ SkASSERT(!approximately_negative(oEndT - oStartT));
+ bool opp = fOperand ^ otherContour->fOperand;
+ if (cancelers && !opp) {
+ // make sure startT and endT have t entries
+ if (startT > 0 || oEndT < 1
+ || thisOne.isMissing(startT) || other.isMissing(oEndT)) {
+ thisOne.addTPair(startT, other, oEndT, true);
+ }
+ if (oStartT > 0 || endT < 1
+ || thisOne.isMissing(endT) || other.isMissing(oStartT)) {
+ other.addTPair(oStartT, thisOne, endT, true);
+ }
+ if (!thisOne.done() && !other.done()) {
+ thisOne.addTCancel(startT, endT, other, oStartT, oEndT);
+ }
+ } else {
+ if (startT > 0 || oStartT > 0
+ || thisOne.isMissing(startT) || other.isMissing(oStartT)) {
+ thisOne.addTPair(startT, other, oStartT, true);
+ }
+ if (endT < 1 || oEndT < 1
+ || thisOne.isMissing(endT) || other.isMissing(oEndT)) {
+ other.addTPair(oEndT, thisOne, endT, true);
+ }
+ if (!thisOne.done() && !other.done()) {
+ thisOne.addTCoincident(startT, endT, other, oStartT, oEndT);
+ }
+ }
+ #if DEBUG_CONCIDENT
+ thisOne.debugShowTs();
+ other.debugShowTs();
+ #endif
+ #if DEBUG_SHOW_WINDING
+ debugShowWindingValues(contourList);
+ #endif
+ }
+ }
+
+ const SkTArray<Segment>& segments() {
+ return fSegments;
+ }
+
+ void setOperand(bool isOp) {
+ fOperand = isOp;
+ }
+
+ void setOppXor(bool isOppXor) {
+ fOppXor = isOppXor;
+ int segmentCount = fSegments.count();
+ for (int test = 0; test < segmentCount; ++test) {
+ fSegments[test].setOppXor(isOppXor);
+ }
+ }
+
+ void setXor(bool isXor) {
+ fXor = isXor;
+ }
+
+ void sortSegments() {
+ int segmentCount = fSegments.count();
+ fSortedSegments.setReserve(segmentCount);
+ for (int test = 0; test < segmentCount; ++test) {
+ *fSortedSegments.append() = &fSegments[test];
+ }
+ QSort<Segment>(fSortedSegments.begin(), fSortedSegments.end() - 1);
+ fFirstSorted = 0;
+ }
+
+ const SkPoint& start() const {
+ return fSegments.front().pts()[0];
+ }
+
+ void toPath(PathWrapper& path) const {
+ int segmentCount = fSegments.count();
+ const SkPoint& pt = fSegments.front().pts()[0];
+ path.deferredMove(pt);
+ for (int test = 0; test < segmentCount; ++test) {
+ fSegments[test].addCurveTo(0, 1, path, true);
+ }
+ path.close();
+ }
+
+ void toPartialBackward(PathWrapper& path) const {
+ int segmentCount = fSegments.count();
+ for (int test = segmentCount - 1; test >= 0; --test) {
+ fSegments[test].addCurveTo(1, 0, path, true);
+ }
+ }
+
+ void toPartialForward(PathWrapper& path) const {
+ int segmentCount = fSegments.count();
+ for (int test = 0; test < segmentCount; ++test) {
+ fSegments[test].addCurveTo(0, 1, path, true);
+ }
+ }
+
+#if 0 // FIXME: obsolete, remove
+ // OPTIMIZATION: feel pretty uneasy about this. It seems like once again
+ // we need to sort and walk edges in y, but that on the surface opens the
+ // same can of worms as before. But then, this is a rough sort based on
+ // segments' top, and not a true sort, so it could be ameniable to regular
+ // sorting instead of linear searching. Still feel like I'm missing something
+ Segment* topSegment(SkScalar& bestY) {
+ int segmentCount = fSegments.count();
+ SkASSERT(segmentCount > 0);
+ int best = -1;
+ Segment* bestSegment = NULL;
+ while (++best < segmentCount) {
+ Segment* testSegment = &fSegments[best];
+ if (testSegment->done()) {
+ continue;
+ }
+ bestSegment = testSegment;
+ break;
+ }
+ if (!bestSegment) {
+ return NULL;
+ }
+ SkScalar bestTop = bestSegment->activeTop();
+ for (int test = best + 1; test < segmentCount; ++test) {
+ Segment* testSegment = &fSegments[test];
+ if (testSegment->done()) {
+ continue;
+ }
+ if (testSegment->bounds().fTop > bestTop) {
+ continue;
+ }
+ SkScalar testTop = testSegment->activeTop();
+ if (bestTop > testTop) {
+ bestTop = testTop;
+ bestSegment = testSegment;
+ }
+ }
+ bestY = bestTop;
+ return bestSegment;
+ }
+#endif
+
+ Segment* topSortableSegment(const SkPoint& topLeft, SkPoint& bestXY) {
+ int segmentCount = fSortedSegments.count();
+ SkASSERT(segmentCount > 0);
+ Segment* bestSegment = NULL;
+ int sortedIndex = fFirstSorted;
+ for ( ; sortedIndex < segmentCount; ++sortedIndex) {
+ Segment* testSegment = fSortedSegments[sortedIndex];
+ if (testSegment->done()) {
+ if (sortedIndex == fFirstSorted) {
+ ++fFirstSorted;
+ }
+ continue;
+ }
+ SkPoint testXY;
+ testSegment->activeLeftTop(testXY);
+ if (testXY.fY < topLeft.fY) {
+ continue;
+ }
+ if (testXY.fY == topLeft.fY && testXY.fX <= topLeft.fX) {
+ continue;
+ }
+ if (bestXY.fY < testXY.fY) {
+ continue;
+ }
+ if (bestXY.fY == testXY.fY && bestXY.fX < testXY.fX) {
+ continue;
+ }
+ bestSegment = testSegment;
+ bestXY = testXY;
+ }
+ return bestSegment;
+ }
+
+ Segment* undoneSegment(int& start, int& end) {
+ int segmentCount = fSegments.count();
+ for (int test = 0; test < segmentCount; ++test) {
+ Segment* testSegment = &fSegments[test];
+ if (testSegment->done()) {
+ continue;
+ }
+ testSegment->undoneSpan(start, end);
+ return testSegment;
+ }
+ return NULL;
+ }
+
+ int updateSegment(int index, const SkPoint* pts) {
+ Segment& segment = fSegments[index];
+ segment.updatePts(pts);
+ return segment.verb() + 1;
+ }
+
+#if DEBUG_TEST
+ SkTArray<Segment>& debugSegments() {
+ return fSegments;
+ }
+#endif
+
+#if DEBUG_DUMP
+ void dump() {
+ int i;
+ const char className[] = "Contour";
+ const int tab = 4;
+ SkDebugf("%s %p (contour=%d)\n", className, this, fID);
+ for (i = 0; i < fSegments.count(); ++i) {
+ SkDebugf("%*s.fSegments[%d]:\n", tab + sizeof(className),
+ className, i);
+ fSegments[i].dump();
+ }
+ SkDebugf("%*s.fBounds=(l:%1.9g, t:%1.9g r:%1.9g, b:%1.9g)\n",
+ tab + sizeof(className), className,
+ fBounds.fLeft, fBounds.fTop,
+ fBounds.fRight, fBounds.fBottom);
+ SkDebugf("%*s.fContainsIntercepts=%d\n", tab + sizeof(className),
+ className, fContainsIntercepts);
+ SkDebugf("%*s.fContainsCurves=%d\n", tab + sizeof(className),
+ className, fContainsCurves);
+ }
+#endif
+
+#if DEBUG_ACTIVE_SPANS
+ void debugShowActiveSpans() {
+ for (int index = 0; index < fSegments.count(); ++index) {
+ fSegments[index].debugShowActiveSpans();
+ }
+ }
+
+ void validateActiveSpans() {
+ for (int index = 0; index < fSegments.count(); ++index) {
+ fSegments[index].validateActiveSpans();
+ }
+ }
+#endif
+
+#if DEBUG_SHOW_WINDING
+ int debugShowWindingValues(int totalSegments, int ofInterest) {
+ int count = fSegments.count();
+ int sum = 0;
+ for (int index = 0; index < count; ++index) {
+ sum += fSegments[index].debugShowWindingValues(totalSegments, ofInterest);
+ }
+ // SkDebugf("%s sum=%d\n", __FUNCTION__, sum);
+ return sum;
+ }
+
+ static void debugShowWindingValues(SkTDArray<Contour*>& contourList) {
+ // int ofInterest = 1 << 1 | 1 << 5 | 1 << 9 | 1 << 13;
+ // int ofInterest = 1 << 4 | 1 << 8 | 1 << 12 | 1 << 16;
+ int ofInterest = 1 << 5 | 1 << 8;
+ int total = 0;
+ int index;
+ for (index = 0; index < contourList.count(); ++index) {
+ total += contourList[index]->segments().count();
+ }
+ int sum = 0;
+ for (index = 0; index < contourList.count(); ++index) {
+ sum += contourList[index]->debugShowWindingValues(total, ofInterest);
+ }
+ // SkDebugf("%s total=%d\n", __FUNCTION__, sum);
+ }
+#endif
+
+protected:
+ void setBounds() {
+ int count = fSegments.count();
+ if (count == 0) {
+ SkDebugf("%s empty contour\n", __FUNCTION__);
+ SkASSERT(0);
+ // FIXME: delete empty contour?
+ return;
+ }
+ fBounds = fSegments.front().bounds();
+ for (int index = 1; index < count; ++index) {
+ fBounds.add(fSegments[index].bounds());
+ }
+ }
+
+private:
+ SkTArray<Segment> fSegments;
+ SkTDArray<Segment*> fSortedSegments;
+ int fFirstSorted;
+ SkTDArray<Coincidence> fCoincidences;
+ SkTDArray<const Contour*> fCrosses;
+ Bounds fBounds;
+ bool fContainsIntercepts;
+ bool fContainsCurves;
+ bool fOperand; // true for the second argument to a binary operator
+ bool fXor;
+ bool fOppXor;
+#if DEBUG_DUMP
+ int fID;
+#endif
+};
+
+class EdgeBuilder {
+public:
+
+EdgeBuilder(const PathWrapper& path, SkTArray<Contour>& contours)
+ : fPath(path.nativePath())
+ , fContours(contours)
+{
+ init();
+}
+
+EdgeBuilder(const SkPath& path, SkTArray<Contour>& contours)
+ : fPath(&path)
+ , fContours(contours)
+{
+ init();
+}
+
+void init() {
+ fCurrentContour = NULL;
+ fOperand = false;
+ fXorMask[0] = fXorMask[1] = (fPath->getFillType() & 1) ? kEvenOdd_Mask : kWinding_Mask;
+#if DEBUG_DUMP
+ gContourID = 0;
+ gSegmentID = 0;
+#endif
+ fSecondHalf = preFetch();
+}
+
+void addOperand(const SkPath& path) {
+ SkASSERT(fPathVerbs.count() > 0 && fPathVerbs.end()[-1] == SkPath::kDone_Verb);
+ fPathVerbs.pop();
+ fPath = &path;
+ fXorMask[1] = (fPath->getFillType() & 1) ? kEvenOdd_Mask : kWinding_Mask;
+ preFetch();
+}
+
+void finish() {
+ walk();
+ complete();
+ if (fCurrentContour && !fCurrentContour->segments().count()) {
+ fContours.pop_back();
+ }
+ // correct pointers in contours since fReducePts may have moved as it grew
+ int cIndex = 0;
+ int extraCount = fExtra.count();
+ SkASSERT(extraCount == 0 || fExtra[0] == -1);
+ int eIndex = 0;
+ int rIndex = 0;
+ while (++eIndex < extraCount) {
+ int offset = fExtra[eIndex];
+ if (offset < 0) {
+ ++cIndex;
+ continue;
+ }
+ fCurrentContour = &fContours[cIndex];
+ rIndex += fCurrentContour->updateSegment(offset - 1,
+ &fReducePts[rIndex]);
+ }
+ fExtra.reset(); // we're done with this
+}
+
+ShapeOpMask xorMask() const {
+ return fXorMask[fOperand];
+}
+
+protected:
+
+void complete() {
+ if (fCurrentContour && fCurrentContour->segments().count()) {
+ fCurrentContour->complete();
+ fCurrentContour = NULL;
+ }
+}
+
+// FIXME:remove once we can access path pts directly
+int preFetch() {
+ SkPath::RawIter iter(*fPath); // FIXME: access path directly when allowed
+ SkPoint pts[4];
+ SkPath::Verb verb;
+ do {
+ verb = iter.next(pts);
+ *fPathVerbs.append() = verb;
+ if (verb == SkPath::kMove_Verb) {
+ *fPathPts.append() = pts[0];
+ } else if (verb >= SkPath::kLine_Verb && verb <= SkPath::kCubic_Verb) {
+ fPathPts.append(verb, &pts[1]);
+ }
+ } while (verb != SkPath::kDone_Verb);
+ return fPathVerbs.count() - 1;
+}
+
+void walk() {
+ SkPath::Verb reducedVerb;
+ uint8_t* verbPtr = fPathVerbs.begin();
+ uint8_t* endOfFirstHalf = &verbPtr[fSecondHalf];
+ const SkPoint* pointsPtr = fPathPts.begin();
+ const SkPoint* finalCurveStart = NULL;
+ const SkPoint* finalCurveEnd = NULL;
+ SkPath::Verb verb;
+ while ((verb = (SkPath::Verb) *verbPtr++) != SkPath::kDone_Verb) {
+ switch (verb) {
+ case SkPath::kMove_Verb:
+ complete();
+ if (!fCurrentContour) {
+ fCurrentContour = fContours.push_back_n(1);
+ fCurrentContour->setOperand(fOperand);
+ fCurrentContour->setXor(fXorMask[fOperand] == kEvenOdd_Mask);
+ *fExtra.append() = -1; // start new contour
+ }
+ finalCurveEnd = pointsPtr++;
+ goto nextVerb;
+ case SkPath::kLine_Verb:
+ // skip degenerate points
+ if (pointsPtr[-1].fX != pointsPtr[0].fX
+ || pointsPtr[-1].fY != pointsPtr[0].fY) {
+ fCurrentContour->addLine(&pointsPtr[-1]);
+ }
+ break;
+ case SkPath::kQuad_Verb:
+
+ reducedVerb = QuadReduceOrder(&pointsPtr[-1], fReducePts);
+ if (reducedVerb == 0) {
+ break; // skip degenerate points
+ }
+ if (reducedVerb == 1) {
+ *fExtra.append() =
+ fCurrentContour->addLine(fReducePts.end() - 2);
+ break;
+ }
+ fCurrentContour->addQuad(&pointsPtr[-1]);
+ break;
+ case SkPath::kCubic_Verb:
+ reducedVerb = CubicReduceOrder(&pointsPtr[-1], fReducePts);
+ if (reducedVerb == 0) {
+ break; // skip degenerate points
+ }
+ if (reducedVerb == 1) {
+ *fExtra.append() =
+ fCurrentContour->addLine(fReducePts.end() - 2);
+ break;
+ }
+ if (reducedVerb == 2) {
+ *fExtra.append() =
+ fCurrentContour->addQuad(fReducePts.end() - 3);
+ break;
+ }
+ fCurrentContour->addCubic(&pointsPtr[-1]);
+ break;
+ case SkPath::kClose_Verb:
+ SkASSERT(fCurrentContour);
+ if (finalCurveStart && finalCurveEnd
+ && *finalCurveStart != *finalCurveEnd) {
+ *fReducePts.append() = *finalCurveStart;
+ *fReducePts.append() = *finalCurveEnd;
+ *fExtra.append() =
+ fCurrentContour->addLine(fReducePts.end() - 2);
+ }
+ complete();
+ goto nextVerb;
+ default:
+ SkDEBUGFAIL("bad verb");
+ return;
+ }
+ finalCurveStart = &pointsPtr[verb - 1];
+ pointsPtr += verb;
+ SkASSERT(fCurrentContour);
+ nextVerb:
+ if (verbPtr == endOfFirstHalf) {
+ fOperand = true;
+ }
+ }
+}
+
+private:
+ const SkPath* fPath;
+ SkTDArray<SkPoint> fPathPts; // FIXME: point directly to path pts instead
+ SkTDArray<uint8_t> fPathVerbs; // FIXME: remove
+ Contour* fCurrentContour;
+ SkTArray<Contour>& fContours;
+ SkTDArray<SkPoint> fReducePts; // segments created on the fly
+ SkTDArray<int> fExtra; // -1 marks new contour, > 0 offsets into contour
+ ShapeOpMask fXorMask[2];
+ int fSecondHalf;
+ bool fOperand;
+};
+
+class Work {
+public:
+ enum SegmentType {
+ kHorizontalLine_Segment = -1,
+ kVerticalLine_Segment = 0,
+ kLine_Segment = SkPath::kLine_Verb,
+ kQuad_Segment = SkPath::kQuad_Verb,
+ kCubic_Segment = SkPath::kCubic_Verb,
+ };
+
+ void addCoincident(Work& other, const Intersections& ts, bool swap) {
+ fContour->addCoincident(fIndex, other.fContour, other.fIndex, ts, swap);
+ }
+
+ // FIXME: does it make sense to write otherIndex now if we're going to
+ // fix it up later?
+ void addOtherT(int index, double otherT, int otherIndex) {
+ fContour->addOtherT(fIndex, index, otherT, otherIndex);
+ }
+
+ // Avoid collapsing t values that are close to the same since
+ // we walk ts to describe consecutive intersections. Since a pair of ts can
+ // be nearly equal, any problems caused by this should be taken care
+ // of later.
+ // On the edge or out of range values are negative; add 2 to get end
+ int addT(double newT, const Work& other) {
+ return fContour->addT(fIndex, newT, other.fContour, other.fIndex);
+ }
+
+ bool advance() {
+ return ++fIndex < fLast;
+ }
+
+ SkScalar bottom() const {
+ return bounds().fBottom;
+ }
+
+ const Bounds& bounds() const {
+ return fContour->segments()[fIndex].bounds();
+ }
+
+ const SkPoint* cubic() const {
+ return fCubic;
+ }
+
+ void init(Contour* contour) {
+ fContour = contour;
+ fIndex = 0;
+ fLast = contour->segments().count();
+ }
+
+ bool isAdjacent(const Work& next) {
+ return fContour == next.fContour && fIndex + 1 == next.fIndex;
+ }
+
+ bool isFirstLast(const Work& next) {
+ return fContour == next.fContour && fIndex == 0
+ && next.fIndex == fLast - 1;
+ }
+
+ SkScalar left() const {
+ return bounds().fLeft;
+ }
+
+ void promoteToCubic() {
+ fCubic[0] = pts()[0];
+ fCubic[2] = pts()[1];
+ fCubic[3] = pts()[2];
+ fCubic[1].fX = (fCubic[0].fX + fCubic[2].fX * 2) / 3;
+ fCubic[1].fY = (fCubic[0].fY + fCubic[2].fY * 2) / 3;
+ fCubic[2].fX = (fCubic[3].fX + fCubic[2].fX * 2) / 3;
+ fCubic[2].fY = (fCubic[3].fY + fCubic[2].fY * 2) / 3;
+ }
+
+ const SkPoint* pts() const {
+ return fContour->segments()[fIndex].pts();
+ }
+
+ SkScalar right() const {
+ return bounds().fRight;
+ }
+
+ ptrdiff_t segmentIndex() const {
+ return fIndex;
+ }
+
+ SegmentType segmentType() const {
+ const Segment& segment = fContour->segments()[fIndex];
+ SegmentType type = (SegmentType) segment.verb();
+ if (type != kLine_Segment) {
+ return type;
+ }
+ if (segment.isHorizontal()) {
+ return kHorizontalLine_Segment;
+ }
+ if (segment.isVertical()) {
+ return kVerticalLine_Segment;
+ }
+ return kLine_Segment;
+ }
+
+ bool startAfter(const Work& after) {
+ fIndex = after.fIndex;
+ return advance();
+ }
+
+ SkScalar top() const {
+ return bounds().fTop;
+ }
+
+ SkPath::Verb verb() const {
+ return fContour->segments()[fIndex].verb();
+ }
+
+ SkScalar x() const {
+ return bounds().fLeft;
+ }
+
+ bool xFlipped() const {
+ return x() != pts()[0].fX;
+ }
+
+ SkScalar y() const {
+ return bounds().fTop;
+ }
+
+ bool yFlipped() const {
+ return y() != pts()[0].fY;
+ }
+
+protected:
+ Contour* fContour;
+ SkPoint fCubic[4];
+ int fIndex;
+ int fLast;
+};
+
+#if DEBUG_ADD_INTERSECTING_TS
+static void debugShowLineIntersection(int pts, const Work& wt,
+ const Work& wn, const double wtTs[2], const double wnTs[2]) {
+ return;
+ if (!pts) {
+ SkDebugf("%s no intersect (%1.9g,%1.9g %1.9g,%1.9g) (%1.9g,%1.9g %1.9g,%1.9g)\n",
+ __FUNCTION__, wt.pts()[0].fX, wt.pts()[0].fY,
+ wt.pts()[1].fX, wt.pts()[1].fY, wn.pts()[0].fX, wn.pts()[0].fY,
+ wn.pts()[1].fX, wn.pts()[1].fY);
+ return;
+ }
+ SkPoint wtOutPt, wnOutPt;
+ LineXYAtT(wt.pts(), wtTs[0], &wtOutPt);
+ LineXYAtT(wn.pts(), wnTs[0], &wnOutPt);
+ SkDebugf("%s wtTs[0]=%1.9g (%1.9g,%1.9g %1.9g,%1.9g) (%1.9g,%1.9g)",
+ __FUNCTION__,
+ wtTs[0], wt.pts()[0].fX, wt.pts()[0].fY,
+ wt.pts()[1].fX, wt.pts()[1].fY, wtOutPt.fX, wtOutPt.fY);
+ if (pts == 2) {
+ SkDebugf(" wtTs[1]=%1.9g", wtTs[1]);
+ }
+ SkDebugf(" wnTs[0]=%g (%1.9g,%1.9g %1.9g,%1.9g) (%1.9g,%1.9g)",
+ wnTs[0], wn.pts()[0].fX, wn.pts()[0].fY,
+ wn.pts()[1].fX, wn.pts()[1].fY, wnOutPt.fX, wnOutPt.fY);
+ if (pts == 2) {
+ SkDebugf(" wnTs[1]=%1.9g", wnTs[1]);
+ }
+ SkDebugf("\n");
+}
+
+static void debugShowQuadLineIntersection(int pts, const Work& wt,
+ const Work& wn, const double wtTs[2], const double wnTs[2]) {
+ if (!pts) {
+ SkDebugf("%s no intersect (%1.9g,%1.9g %1.9g,%1.9g %1.9g,%1.9g)"
+ " (%1.9g,%1.9g %1.9g,%1.9g)\n",
+ __FUNCTION__, wt.pts()[0].fX, wt.pts()[0].fY,
+ wt.pts()[1].fX, wt.pts()[1].fY, wt.pts()[2].fX, wt.pts()[2].fY,
+ wn.pts()[0].fX, wn.pts()[0].fY, wn.pts()[1].fX, wn.pts()[1].fY);
+ return;
+ }
+ SkPoint wtOutPt, wnOutPt;
+ QuadXYAtT(wt.pts(), wtTs[0], &wtOutPt);
+ LineXYAtT(wn.pts(), wnTs[0], &wnOutPt);
+ SkDebugf("%s wtTs[0]=%1.9g (%1.9g,%1.9g %1.9g,%1.9g %1.9g,%1.9g) (%1.9g,%1.9g)",
+ __FUNCTION__,
+ wtTs[0], wt.pts()[0].fX, wt.pts()[0].fY,
+ wt.pts()[1].fX, wt.pts()[1].fY, wt.pts()[2].fX, wt.pts()[2].fY,
+ wtOutPt.fX, wtOutPt.fY);
+ if (pts == 2) {
+ QuadXYAtT(wt.pts(), wtTs[1], &wtOutPt);
+ SkDebugf(" wtTs[1]=%1.9g (%1.9g,%1.9g)", wtTs[1], wtOutPt.fX, wtOutPt.fY);
+ }
+ SkDebugf(" wnTs[0]=%g (%1.9g,%1.9g %1.9g,%1.9g) (%1.9g,%1.9g)",
+ wnTs[0], wn.pts()[0].fX, wn.pts()[0].fY,
+ wn.pts()[1].fX, wn.pts()[1].fY, wnOutPt.fX, wnOutPt.fY);
+ if (pts == 2) {
+ LineXYAtT(wn.pts(), wnTs[1], &wnOutPt);
+ SkDebugf(" wnTs[1]=%1.9g (%1.9g,%1.9g)", wnTs[1], wnOutPt.fX, wnOutPt.fY);
+ }
+ SkDebugf("\n");
+}
+
+static void debugShowQuadIntersection(int pts, const Work& wt,
+ const Work& wn, const double wtTs[2], const double wnTs[2]) {
+ if (!pts) {
+ SkDebugf("%s no intersect (%1.9g,%1.9g %1.9g,%1.9g %1.9g,%1.9g)"
+ " (%1.9g,%1.9g %1.9g,%1.9g %1.9g,%1.9g)\n",
+ __FUNCTION__, wt.pts()[0].fX, wt.pts()[0].fY,
+ wt.pts()[1].fX, wt.pts()[1].fY, wt.pts()[2].fX, wt.pts()[2].fY,
+ wn.pts()[0].fX, wn.pts()[0].fY, wn.pts()[1].fX, wn.pts()[1].fY,
+ wn.pts()[2].fX, wn.pts()[2].fY );
+ return;
+ }
+ SkPoint wtOutPt, wnOutPt;
+ QuadXYAtT(wt.pts(), wtTs[0], &wtOutPt);
+ QuadXYAtT(wn.pts(), wnTs[0], &wnOutPt);
+ SkDebugf("%s wtTs[0]=%1.9g (%1.9g,%1.9g %1.9g,%1.9g %1.9g,%1.9g) (%1.9g,%1.9g)",
+ __FUNCTION__,
+ wtTs[0], wt.pts()[0].fX, wt.pts()[0].fY,
+ wt.pts()[1].fX, wt.pts()[1].fY, wt.pts()[2].fX, wt.pts()[2].fY,
+ wtOutPt.fX, wtOutPt.fY);
+ if (pts == 2) {
+ SkDebugf(" wtTs[1]=%1.9g", wtTs[1]);
+ }
+ SkDebugf(" wnTs[0]=%g (%1.9g,%1.9g %1.9g,%1.9g %1.9g,%1.9g) (%1.9g,%1.9g)",
+ wnTs[0], wn.pts()[0].fX, wn.pts()[0].fY,
+ wn.pts()[1].fX, wn.pts()[1].fY, wn.pts()[2].fX, wn.pts()[2].fY,
+ wnOutPt.fX, wnOutPt.fY);
+ if (pts == 2) {
+ SkDebugf(" wnTs[1]=%1.9g", wnTs[1]);
+ }
+ SkDebugf("\n");
+}
+#else
+static void debugShowLineIntersection(int , const Work& ,
+ const Work& , const double [2], const double [2]) {
+}
+
+static void debugShowQuadLineIntersection(int , const Work& ,
+ const Work& , const double [2], const double [2]) {
+}
+
+static void debugShowQuadIntersection(int , const Work& ,
+ const Work& , const double [2], const double [2]) {
+}
+#endif
+
+static bool addIntersectTs(Contour* test, Contour* next) {
+
+ if (test != next) {
+ if (test->bounds().fBottom < next->bounds().fTop) {
+ return false;
+ }
+ if (!Bounds::Intersects(test->bounds(), next->bounds())) {
+ return true;
+ }
+ }
+ Work wt;
+ wt.init(test);
+ bool foundCommonContour = test == next;
+ do {
+ Work wn;
+ wn.init(next);
+ if (test == next && !wn.startAfter(wt)) {
+ continue;
+ }
+ do {
+ if (!Bounds::Intersects(wt.bounds(), wn.bounds())) {
+ continue;
+ }
+ int pts;
+ Intersections ts;
+ bool swap = false;
+ switch (wt.segmentType()) {
+ case Work::kHorizontalLine_Segment:
+ swap = true;
+ switch (wn.segmentType()) {
+ case Work::kHorizontalLine_Segment:
+ case Work::kVerticalLine_Segment:
+ case Work::kLine_Segment: {
+ pts = HLineIntersect(wn.pts(), wt.left(),
+ wt.right(), wt.y(), wt.xFlipped(), ts);
+ debugShowLineIntersection(pts, wt, wn,
+ ts.fT[1], ts.fT[0]);
+ break;
+ }
+ case Work::kQuad_Segment: {
+ pts = HQuadIntersect(wn.pts(), wt.left(),
+ wt.right(), wt.y(), wt.xFlipped(), ts);
+ break;
+ }
+ case Work::kCubic_Segment: {
+ pts = HCubicIntersect(wn.pts(), wt.left(),
+ wt.right(), wt.y(), wt.xFlipped(), ts);
+ break;
+ }
+ default:
+ SkASSERT(0);
+ }
+ break;
+ case Work::kVerticalLine_Segment:
+ swap = true;
+ switch (wn.segmentType()) {
+ case Work::kHorizontalLine_Segment:
+ case Work::kVerticalLine_Segment:
+ case Work::kLine_Segment: {
+ pts = VLineIntersect(wn.pts(), wt.top(),
+ wt.bottom(), wt.x(), wt.yFlipped(), ts);
+ debugShowLineIntersection(pts, wt, wn,
+ ts.fT[1], ts.fT[0]);
+ break;
+ }
+ case Work::kQuad_Segment: {
+ pts = VQuadIntersect(wn.pts(), wt.top(),
+ wt.bottom(), wt.x(), wt.yFlipped(), ts);
+ break;
+ }
+ case Work::kCubic_Segment: {
+ pts = VCubicIntersect(wn.pts(), wt.top(),
+ wt.bottom(), wt.x(), wt.yFlipped(), ts);
+ break;
+ }
+ default:
+ SkASSERT(0);
+ }
+ break;
+ case Work::kLine_Segment:
+ switch (wn.segmentType()) {
+ case Work::kHorizontalLine_Segment:
+ pts = HLineIntersect(wt.pts(), wn.left(),
+ wn.right(), wn.y(), wn.xFlipped(), ts);
+ debugShowLineIntersection(pts, wt, wn,
+ ts.fT[1], ts.fT[0]);
+ break;
+ case Work::kVerticalLine_Segment:
+ pts = VLineIntersect(wt.pts(), wn.top(),
+ wn.bottom(), wn.x(), wn.yFlipped(), ts);
+ debugShowLineIntersection(pts, wt, wn,
+ ts.fT[1], ts.fT[0]);
+ break;
+ case Work::kLine_Segment: {
+ pts = LineIntersect(wt.pts(), wn.pts(), ts);
+ debugShowLineIntersection(pts, wt, wn,
+ ts.fT[1], ts.fT[0]);
+ break;
+ }
+ case Work::kQuad_Segment: {
+ swap = true;
+ pts = QuadLineIntersect(wn.pts(), wt.pts(), ts);
+ debugShowQuadLineIntersection(pts, wn, wt,
+ ts.fT[0], ts.fT[1]);
+ break;
+ }
+ case Work::kCubic_Segment: {
+ swap = true;
+ pts = CubicLineIntersect(wn.pts(), wt.pts(), ts);
+ break;
+ }
+ default:
+ SkASSERT(0);
+ }
+ break;
+ case Work::kQuad_Segment:
+ switch (wn.segmentType()) {
+ case Work::kHorizontalLine_Segment:
+ pts = HQuadIntersect(wt.pts(), wn.left(),
+ wn.right(), wn.y(), wn.xFlipped(), ts);
+ break;
+ case Work::kVerticalLine_Segment:
+ pts = VQuadIntersect(wt.pts(), wn.top(),
+ wn.bottom(), wn.x(), wn.yFlipped(), ts);
+ break;
+ case Work::kLine_Segment: {
+ pts = QuadLineIntersect(wt.pts(), wn.pts(), ts);
+ debugShowQuadLineIntersection(pts, wt, wn,
+ ts.fT[0], ts.fT[1]);
+ break;
+ }
+ case Work::kQuad_Segment: {
+ pts = QuadIntersect(wt.pts(), wn.pts(), ts);
+ debugShowQuadIntersection(pts, wt, wn,
+ ts.fT[0], ts.fT[1]);
+ break;
+ }
+ case Work::kCubic_Segment: {
+ wt.promoteToCubic();
+ pts = CubicIntersect(wt.cubic(), wn.pts(), ts);
+ break;
+ }
+ default:
+ SkASSERT(0);
+ }
+ break;
+ case Work::kCubic_Segment:
+ switch (wn.segmentType()) {
+ case Work::kHorizontalLine_Segment:
+ pts = HCubicIntersect(wt.pts(), wn.left(),
+ wn.right(), wn.y(), wn.xFlipped(), ts);
+ break;
+ case Work::kVerticalLine_Segment:
+ pts = VCubicIntersect(wt.pts(), wn.top(),
+ wn.bottom(), wn.x(), wn.yFlipped(), ts);
+ break;
+ case Work::kLine_Segment: {
+ pts = CubicLineIntersect(wt.pts(), wn.pts(), ts);
+ break;
+ }
+ case Work::kQuad_Segment: {
+ wn.promoteToCubic();
+ pts = CubicIntersect(wt.pts(), wn.cubic(), ts);
+ break;
+ }
+ case Work::kCubic_Segment: {
+ pts = CubicIntersect(wt.pts(), wn.pts(), ts);
+ break;
+ }
+ default:
+ SkASSERT(0);
+ }
+ break;
+ default:
+ SkASSERT(0);
+ }
+ if (!foundCommonContour && pts > 0) {
+ test->addCross(next);
+ next->addCross(test);
+ foundCommonContour = true;
+ }
+ // in addition to recording T values, record matching segment
+ if (pts == 2) {
+ if (wn.segmentType() <= Work::kLine_Segment
+ && wt.segmentType() <= Work::kLine_Segment) {
+ wt.addCoincident(wn, ts, swap);
+ continue;
+ }
+ if (wn.segmentType() == Work::kQuad_Segment
+ && wt.segmentType() == Work::kQuad_Segment
+ && ts.coincidentUsed() == 2) {
+ wt.addCoincident(wn, ts, swap);
+ continue;
+ }
+
+ }
+ for (int pt = 0; pt < pts; ++pt) {
+ SkASSERT(ts.fT[0][pt] >= 0 && ts.fT[0][pt] <= 1);
+ SkASSERT(ts.fT[1][pt] >= 0 && ts.fT[1][pt] <= 1);
+ int testTAt = wt.addT(ts.fT[swap][pt], wn);
+ int nextTAt = wn.addT(ts.fT[!swap][pt], wt);
+ wt.addOtherT(testTAt, ts.fT[!swap][pt ^ ts.fFlip], nextTAt);
+ wn.addOtherT(nextTAt, ts.fT[swap][pt ^ ts.fFlip], testTAt);
+ }
+ } while (wn.advance());
+ } while (wt.advance());
+ return true;
+}
+
+// resolve any coincident pairs found while intersecting, and
+// see if coincidence is formed by clipping non-concident segments
+static void coincidenceCheck(SkTDArray<Contour*>& contourList, int total) {
+ int contourCount = contourList.count();
+ for (int cIndex = 0; cIndex < contourCount; ++cIndex) {
+ Contour* contour = contourList[cIndex];
+ contour->resolveCoincidence(contourList);
+ }
+ for (int cIndex = 0; cIndex < contourCount; ++cIndex) {
+ Contour* contour = contourList[cIndex];
+ contour->findTooCloseToCall();
+ }
+}
+
+static int contourRangeCheckX(SkTDArray<Contour*>& contourList, double mid,
+ const Segment* current, int index, int endIndex, bool opp) {
+ const SkPoint& basePt = current->xyAtT(endIndex);
+ int contourCount = contourList.count();
+ SkScalar bestX = SK_ScalarMin;
+ const Segment* test = NULL;
+ int tIndex;
+ double tHit;
+ bool crossOpp;
+ for (int cTest = 0; cTest < contourCount; ++cTest) {
+ Contour* contour = contourList[cTest];
+ bool testOpp = contour->operand() ^ current->operand() ^ opp;
+ if (basePt.fX < contour->bounds().fLeft) {
+ continue;
+ }
+ if (bestX > contour->bounds().fRight) {
+ continue;
+ }
+ const Segment* next = contour->crossedSegmentX(basePt, bestX, tIndex, tHit, testOpp);
+ if (next) {
+ test = next;
+ crossOpp = testOpp;
+ }
+ }
+ if (!test) {
+ return 0;
+ }
+ return test->windingAtT(tHit, tIndex, crossOpp);
+}
+
+static int contourRangeCheckY(SkTDArray<Contour*>& contourList, double mid,
+ const Segment* current, int index, int endIndex, bool opp) {
+ const SkPoint& basePt = current->xyAtT(endIndex);
+ int contourCount = contourList.count();
+ SkScalar bestY = SK_ScalarMin;
+ const Segment* test = NULL;
+ int tIndex;
+ double tHit;
+ bool crossOpp;
+ for (int cTest = 0; cTest < contourCount; ++cTest) {
+ Contour* contour = contourList[cTest];
+ bool testOpp = contour->operand() ^ current->operand() ^ opp;
+ if (basePt.fY < contour->bounds().fTop) {
+ continue;
+ }
+ if (bestY > contour->bounds().fBottom) {
+ continue;
+ }
+ const Segment* next = contour->crossedSegmentY(basePt, bestY, tIndex, tHit, testOpp);
+ if (next) {
+ test = next;
+ crossOpp = testOpp;
+ }
+ }
+ if (!test) {
+ return 0;
+ }
+ return test->windingAtT(tHit, tIndex, crossOpp);
+}
+
+// project a ray from the top of the contour up and see if it hits anything
+// note: when we compute line intersections, we keep track of whether
+// two contours touch, so we need only look at contours not touching this one.
+// OPTIMIZATION: sort contourList vertically to avoid linear walk
+static int innerContourCheck(SkTDArray<Contour*>& contourList,
+ const Segment* current, int index, int endIndex, bool opp) {
+ const SkPoint& basePt = current->xyAtT(endIndex);
+ int contourCount = contourList.count();
+ SkScalar bestY = SK_ScalarMin;
+ const Segment* test = NULL;
+ int tIndex;
+ double tHit;
+ bool crossOpp;
+ for (int cTest = 0; cTest < contourCount; ++cTest) {
+ Contour* contour = contourList[cTest];
+ bool testOpp = contour->operand() ^ current->operand() ^ opp;
+ if (basePt.fY < contour->bounds().fTop) {
+ continue;
+ }
+ if (bestY > contour->bounds().fBottom) {
+ continue;
+ }
+ const Segment* next = contour->crossedSegmentY(basePt, bestY, tIndex, tHit, testOpp);
+ if (next) {
+ test = next;
+ crossOpp = testOpp;
+ }
+ }
+ if (!test) {
+ return 0;
+ }
+ int winding, windValue;
+ // If the ray hit the end of a span, we need to construct the wheel of
+ // angles to find the span closest to the ray -- even if there are just
+ // two spokes on the wheel.
+ const Angle* angle = NULL;
+ if (approximately_zero(tHit - test->t(tIndex))) {
+ SkTDArray<Angle> angles;
+ int end = test->nextSpan(tIndex, 1);
+ if (end < 0) {
+ end = test->nextSpan(tIndex, -1);
+ }
+ test->addTwoAngles(end, tIndex, angles);
+ SkASSERT(angles.count() > 0);
+ if (angles[0].segment()->yAtT(angles[0].start()) >= basePt.fY) {
+#if DEBUG_SORT
+ SkDebugf("%s early return\n", __FUNCTION__);
+#endif
+ return SK_MinS32;
+ }
+ test->buildAngles(tIndex, angles, false);
+ SkTDArray<Angle*> sorted;
+ // OPTIMIZATION: call a sort that, if base point is the leftmost,
+ // returns the first counterclockwise hour before 6 o'clock,
+ // or if the base point is rightmost, returns the first clockwise
+ // hour after 6 o'clock
+ bool sortable = Segment::SortAngles(angles, sorted);
+ if (!sortable) {
+ return SK_MinS32;
+ }
+#if DEBUG_SORT
+ sorted[0]->segment()->debugShowSort(__FUNCTION__, sorted, 0, 0, 0);
+#endif
+ // walk the sorted angle fan to find the lowest angle
+ // above the base point. Currently, the first angle in the sorted array
+ // is 12 noon or an earlier hour (the next counterclockwise)
+ int count = sorted.count();
+ int left = -1;
+ int mid = -1;
+ int right = -1;
+ bool baseMatches = test->yAtT(tIndex) == basePt.fY;
+ for (int index = 0; index < count; ++index) {
+ angle = sorted[index];
+ if (angle->unsortable()) {
+ continue;
+ }
+ if (baseMatches && angle->isHorizontal()) {
+ continue;
+ }
+ double indexDx = angle->dx();
+ test = angle->segment();
+ if (test->verb() > SkPath::kLine_Verb && approximately_zero(indexDx)) {
+ const SkPoint* pts = test->pts();
+ indexDx = pts[2].fX - pts[1].fX - indexDx;
+ }
+ if (indexDx < 0) {
+ left = index;
+ } else if (indexDx > 0) {
+ right = index;
+ int previous = index - 1;
+ if (previous < 0) {
+ previous = count - 1;
+ }
+ const Angle* prev = sorted[previous];
+ if (prev->dy() >= 0 && prev->dx() > 0 && angle->dy() < 0) {
+#if DEBUG_SORT
+ SkDebugf("%s use prev\n", __FUNCTION__);
+#endif
+ right = previous;
+ }
+ break;
+ } else {
+ mid = index;
+ }
+ }
+ if ((left < 0 || right < 0) && mid >= 0) {
+ angle = sorted[mid];
+ Segment* midSeg = angle->segment();
+ int end = angle->end();
+ if (midSeg->unsortable(end)) {
+ return SK_MinS32;
+ }
+ }
+ if (left < 0 && right < 0) {
+ left = mid;
+ }
+ if (left < 0 && right < 0) {
+ SkASSERT(0);
+ return SK_MinS32; // unsortable
+ }
+ if (left < 0) {
+ left = right;
+ } else if (left >= 0 && mid >= 0 && right >= 0
+ && sorted[mid]->sign() == sorted[right]->sign()) {
+ left = right;
+ }
+ angle = sorted[left];
+ test = angle->segment();
+ winding = crossOpp ? test->oppSum(angle) : test->windSum(angle);
+ SkASSERT(winding != SK_MinS32);
+ windValue = crossOpp ? test->oppValue(angle) : test->windValue(angle);
+#if DEBUG_WINDING
+ SkDebugf("%s angle winding=%d windValue=%d sign=%d\n", __FUNCTION__, winding,
+ windValue, angle->sign());
+#endif
+ } else {
+ winding = crossOpp ? test->oppSum(tIndex) : test->windSum(tIndex);
+ if (winding == SK_MinS32) {
+ return SK_MinS32; // unsortable
+ }
+ windValue = crossOpp ? test->oppValue(tIndex) : test->windValue(tIndex);
+#if DEBUG_WINDING
+ SkDebugf("%s single winding=%d windValue=%d\n", __FUNCTION__, winding,
+ windValue);
+#endif
+ }
+ // see if a + change in T results in a +/- change in X (compute x'(T))
+ SkScalar dx = (*SegmentDXAtT[test->verb()])(test->pts(), tHit);
+ if (test->verb() > SkPath::kLine_Verb && approximately_zero(dx)) {
+ const SkPoint* pts = test->pts();
+ dx = pts[2].fX - pts[1].fX - dx;
+ }
+#if DEBUG_WINDING
+ SkDebugf("%s dx=%1.9g\n", __FUNCTION__, dx);
+#endif
+ SkASSERT(dx != 0);
+ if (winding * dx > 0) { // if same signs, result is negative
+ winding += dx > 0 ? -windValue : windValue;
+#if DEBUG_WINDING
+ SkDebugf("%s final winding=%d\n", __FUNCTION__, winding);
+#endif
+ }
+ return winding;
+}
+
+static Segment* findUndone(SkTDArray<Contour*>& contourList, int& start, int& end) {
+ int contourCount = contourList.count();
+ Segment* result;
+ for (int cIndex = 0; cIndex < contourCount; ++cIndex) {
+ Contour* contour = contourList[cIndex];
+ result = contour->undoneSegment(start, end);
+ if (result) {
+ return result;
+ }
+ }
+ return NULL;
+}
+
+
+
+static Segment* findChase(SkTDArray<Span*>& chase, int& tIndex, int& endIndex) {
+ while (chase.count()) {
+ Span* span;
+ chase.pop(&span);
+ const Span& backPtr = span->fOther->span(span->fOtherIndex);
+ Segment* segment = backPtr.fOther;
+ tIndex = backPtr.fOtherIndex;
+ SkTDArray<Angle> angles;
+ int done = 0;
+ if (segment->activeAngle(tIndex, done, angles)) {
+ Angle* last = angles.end() - 1;
+ tIndex = last->start();
+ endIndex = last->end();
+ #if TRY_ROTATE
+ *chase.insert(0) = span;
+ #else
+ *chase.append() = span;
+ #endif
+ return last->segment();
+ }
+ if (done == angles.count()) {
+ continue;
+ }
+ SkTDArray<Angle*> sorted;
+ bool sortable = Segment::SortAngles(angles, sorted);
+#if DEBUG_SORT
+ sorted[0]->segment()->debugShowSort(__FUNCTION__, sorted, 0, 0, 0);
+#endif
+ if (!sortable) {
+ continue;
+ }
+ // find first angle, initialize winding to computed fWindSum
+ int firstIndex = -1;
+ const Angle* angle;
+ int winding;
+ do {
+ angle = sorted[++firstIndex];
+ segment = angle->segment();
+ winding = segment->windSum(angle);
+ } while (winding == SK_MinS32);
+ int spanWinding = segment->spanSign(angle->start(), angle->end());
+ #if DEBUG_WINDING
+ SkDebugf("%s winding=%d spanWinding=%d\n",
+ __FUNCTION__, winding, spanWinding);
+ #endif
+ // turn span winding into contour winding
+ if (spanWinding * winding < 0) {
+ winding += spanWinding;
+ }
+ #if DEBUG_SORT
+ segment->debugShowSort(__FUNCTION__, sorted, firstIndex, winding, 0);
+ #endif
+ // we care about first sign and whether wind sum indicates this
+ // edge is inside or outside. Maybe need to pass span winding
+ // or first winding or something into this function?
+ // advance to first undone angle, then return it and winding
+ // (to set whether edges are active or not)
+ int nextIndex = firstIndex + 1;
+ int angleCount = sorted.count();
+ int lastIndex = firstIndex != 0 ? firstIndex : angleCount;
+ angle = sorted[firstIndex];
+ winding -= angle->segment()->spanSign(angle);
+ do {
+ SkASSERT(nextIndex != firstIndex);
+ if (nextIndex == angleCount) {
+ nextIndex = 0;
+ }
+ angle = sorted[nextIndex];
+ segment = angle->segment();
+ int maxWinding = winding;
+ winding -= segment->spanSign(angle);
+ #if DEBUG_SORT
+ SkDebugf("%s id=%d maxWinding=%d winding=%d sign=%d\n", __FUNCTION__,
+ segment->debugID(), maxWinding, winding, angle->sign());
+ #endif
+ tIndex = angle->start();
+ endIndex = angle->end();
+ int lesser = SkMin32(tIndex, endIndex);
+ const Span& nextSpan = segment->span(lesser);
+ if (!nextSpan.fDone) {
+#if 1
+ // FIXME: this be wrong. assign startWinding if edge is in
+ // same direction. If the direction is opposite, winding to
+ // assign is flipped sign or +/- 1?
+ if (useInnerWinding(maxWinding, winding)) {
+ maxWinding = winding;
+ }
+ segment->markWinding(lesser, maxWinding);
+#endif
+ break;
+ }
+ } while (++nextIndex != lastIndex);
+ #if TRY_ROTATE
+ *chase.insert(0) = span;
+ #else
+ *chase.append() = span;
+ #endif
+ return segment;
+ }
+ return NULL;
+}
+
+#if DEBUG_ACTIVE_SPANS
+static void debugShowActiveSpans(SkTDArray<Contour*>& contourList) {
+ int index;
+ for (index = 0; index < contourList.count(); ++ index) {
+ contourList[index]->debugShowActiveSpans();
+ }
+ for (index = 0; index < contourList.count(); ++ index) {
+ contourList[index]->validateActiveSpans();
+ }
+}
+#endif
+
+static bool windingIsActive(int winding, int spanWinding) {
+ // FIXME: !spanWinding test must be superflorous, true?
+ return winding * spanWinding <= 0 && abs(winding) <= abs(spanWinding)
+ && (!winding || !spanWinding || winding == -spanWinding);
+}
+
+static Segment* findSortableTop(SkTDArray<Contour*>& contourList, int& index,
+ int& endIndex, SkPoint& topLeft, bool& unsortable, bool onlySortable) {
+ Segment* result;
+ do {
+ SkPoint bestXY = {SK_ScalarMax, SK_ScalarMax};
+ int contourCount = contourList.count();
+ Segment* topStart = NULL;
+ for (int cIndex = 0; cIndex < contourCount; ++cIndex) {
+ Contour* contour = contourList[cIndex];
+ const Bounds& bounds = contour->bounds();
+ if (bounds.fBottom < topLeft.fY) {
+ continue;
+ }
+ if (bounds.fBottom == topLeft.fY && bounds.fRight < topLeft.fX) {
+ continue;
+ }
+ Segment* test = contour->topSortableSegment(topLeft, bestXY);
+ if (test) {
+ topStart = test;
+ }
+ }
+ if (!topStart) {
+ return NULL;
+ }
+ topLeft = bestXY;
+ result = topStart->findTop(index, endIndex, unsortable, onlySortable);
+ } while (!result);
+ return result;
+}
+
+static int updateWindings(const Segment* current, int index, int endIndex, int& spanWinding) {
+ int lesser = SkMin32(index, endIndex);
+ spanWinding = current->spanSign(index, endIndex);
+ int winding = current->windSum(lesser);
+ bool inner = useInnerWinding(winding - spanWinding, winding);
+#if DEBUG_WINDING
+ SkDebugf("%s id=%d t=%1.9g spanWinding=%d winding=%d sign=%d"
+ " inner=%d result=%d\n",
+ __FUNCTION__, current->debugID(), current->t(lesser),
+ spanWinding, winding, SkSign32(index - endIndex),
+ useInnerWinding(winding - spanWinding, winding),
+ inner ? winding - spanWinding : winding);
+#endif
+ if (inner) {
+ winding -= spanWinding;
+ }
+ return winding;
+}
+
+typedef int (*RangeChecker)(SkTDArray<Contour*>& contourList, double mid,
+ const Segment* current, int index, int endIndex, bool opp);
+
+static int rightAngleWinding(RangeChecker rangeChecker, SkTDArray<Contour*>& contourList,
+ Segment* current, int index, int endIndex, bool opp) {
+ double test = 0.9;
+ int contourWinding;
+ do {
+ contourWinding = (*rangeChecker)(contourList, test, current, index, endIndex, opp);
+ if (contourWinding != SK_MinS32) {
+ return contourWinding;
+ }
+ test /= 2;
+ } while (!approximately_negative(test));
+ SkASSERT(0); // should be OK to comment out, but interested when this hits
+ return contourWinding;
+}
+
+static Segment* tryRightAngleRay(SkTDArray<Contour*>& contourList, int& index,
+ int& endIndex, SkPoint& topLeft, bool& unsortable, RangeChecker& rangeChecker) {
+ // the simple upward projection of the unresolved points hit unsortable angles
+ // shoot rays at right angles to the segment to find its winding, ignoring angle cases
+ topLeft.fX = topLeft.fY = SK_ScalarMin;
+ Segment* current;
+ do {
+ current = findSortableTop(contourList, index, endIndex, topLeft, unsortable, false);
+ SkASSERT(current); // FIXME: return to caller that path cannot be simplified (yet)
+ // find bounds
+ Bounds bounds;
+ bounds.setPoint(current->xyAtT(index));
+ bounds.add(current->xyAtT(endIndex));
+ SkScalar width = bounds.width();
+ SkScalar height = bounds.height();
+ if (width > height) {
+ if (approximately_negative(width)) {
+ continue; // edge is too small to resolve meaningfully
+ }
+ rangeChecker = contourRangeCheckY;
+ } else {
+ if (approximately_negative(height)) {
+ continue; // edge is too small to resolve meaningfully
+ }
+ rangeChecker = contourRangeCheckX;
+ }
+ } while (!current);
+ return current;
+}
+
+static Segment* findSortableTopOld(SkTDArray<Contour*>& contourList, bool& firstContour, int& index,
+ int& endIndex, SkPoint& topLeft, int& contourWinding, bool& unsortable) {
+ Segment* current;
+ do {
+ current = findSortableTop(contourList, index, endIndex, topLeft, unsortable, true);
+ if (!current) {
+ break;
+ }
+ if (firstContour) {
+ contourWinding = 0;
+ firstContour = false;
+ break;
+ }
+ int sumWinding = current->windSum(SkMin32(index, endIndex));
+ // FIXME: don't I have to adjust windSum to get contourWinding?
+ if (sumWinding == SK_MinS32) {
+ sumWinding = current->computeSum(index, endIndex, false);
+ }
+ if (sumWinding == SK_MinS32) {
+ contourWinding = innerContourCheck(contourList, current, index, endIndex, false);
+ } else {
+ contourWinding = sumWinding;
+ int spanWinding = current->spanSign(index, endIndex);
+ bool inner = useInnerWinding(sumWinding - spanWinding, sumWinding);
+ if (inner) {
+ contourWinding -= spanWinding;
+ }
+#if DEBUG_WINDING
+ SkDebugf("%s sumWinding=%d spanWinding=%d sign=%d inner=%d result=%d\n",
+ __FUNCTION__, sumWinding, spanWinding, SkSign32(index - endIndex),
+ inner, contourWinding);
+#endif
+ }
+ } while (contourWinding == SK_MinS32);
+ if (contourWinding != SK_MinS32) {
+#if DEBUG_WINDING
+ SkDebugf("%s contourWinding=%d\n", __FUNCTION__, contourWinding);
+#endif
+ return current;
+ }
+ RangeChecker rangeChecker = NULL;
+ current = tryRightAngleRay(contourList, index, endIndex, topLeft, unsortable, rangeChecker);
+ contourWinding = rightAngleWinding(rangeChecker, contourList, current, index, endIndex, false);
+ return current;
+}
+
+// Each segment may have an inside or an outside. Segments contained within
+// winding may have insides on either side, and form a contour that should be
+// ignored. Segments that are coincident with opposing direction segments may
+// have outsides on either side, and should also disappear.
+// 'Normal' segments will have one inside and one outside. Subsequent connections
+// when winding should follow the intersection direction. If more than one edge
+// is an option, choose first edge that continues the inside.
+ // since we start with leftmost top edge, we'll traverse through a
+ // smaller angle counterclockwise to get to the next edge.
+// returns true if all edges were processed
+static bool bridgeWinding(SkTDArray<Contour*>& contourList, PathWrapper& simple) {
+ bool firstContour = true;
+ bool unsortable = false;
+ bool topUnsortable = false;
+ bool firstRetry = false;
+ bool closable = true;
+ SkPoint topLeft = {SK_ScalarMin, SK_ScalarMin};
+ do {
+ int index, endIndex;
+ // iterates while top is unsortable
+ int contourWinding;
+ Segment* current = findSortableTopOld(contourList, firstContour, index, endIndex, topLeft,
+ contourWinding, topUnsortable);
+ if (!current) {
+ if (topUnsortable) {
+ topUnsortable = false;
+ SkASSERT(!firstRetry);
+ firstRetry = true;
+ topLeft.fX = topLeft.fY = SK_ScalarMin;
+ continue;
+ }
+ break;
+ }
+ int winding = contourWinding;
+ int spanWinding = current->spanSign(index, endIndex);
+ // FIXME: needs work. While it works in limited situations, it does
+ // not always compute winding correctly. Active should be removed and instead
+ // the initial winding should be correctly passed in so that if the
+ // inner contour is wound the same way, it never finds an accumulated
+ // winding of zero. Inside 'find next', we need to look for transitions
+ // other than zero when resolving sorted angles.
+ SkTDArray<Span*> chaseArray;
+ do {
+ bool active = windingIsActive(winding, spanWinding);
+ #if DEBUG_WINDING
+ SkDebugf("%s active=%s winding=%d spanWinding=%d\n",
+ __FUNCTION__, active ? "true" : "false",
+ winding, spanWinding);
+ #endif
+ do {
+ SkASSERT(unsortable || !current->done());
+ int nextStart = index;
+ int nextEnd = endIndex;
+ Segment* next = current->findNextWinding(chaseArray, active,
+ nextStart, nextEnd, winding, spanWinding, unsortable);
+ if (!next) {
+ if (active && !unsortable && simple.hasMove()
+ && current->verb() != SkPath::kLine_Verb
+ && !simple.isClosed()) {
+ current->addCurveTo(index, endIndex, simple, true);
+ SkASSERT(simple.isClosed());
+ }
+ break;
+ }
+ #if DEBUG_FLOW
+ SkDebugf("%s current id=%d from=(%1.9g,%1.9g) to=(%1.9g,%1.9g)\n", __FUNCTION__,
+ current->debugID(), current->xyAtT(index).fX, current->xyAtT(index).fY,
+ current->xyAtT(endIndex).fX, current->xyAtT(endIndex).fY);
+ #endif
+ current->addCurveTo(index, endIndex, simple, active);
+ current = next;
+ index = nextStart;
+ endIndex = nextEnd;
+ } while (!simple.isClosed()
+ && ((active && !unsortable) || !current->done()));
+ if (active) {
+ if (!simple.isClosed()) {
+ SkASSERT(unsortable);
+ int min = SkMin32(index, endIndex);
+ if (!current->done(min)) {
+ current->addCurveTo(index, endIndex, simple, true);
+ current->markDone(min, winding ? winding : spanWinding);
+ }
+ closable = false;
+ }
+ simple.close();
+ }
+ current = findChase(chaseArray, index, endIndex);
+ #if DEBUG_ACTIVE_SPANS
+ debugShowActiveSpans(contourList);
+ #endif
+ if (!current) {
+ break;
+ }
+ winding = updateWindings(current, index, endIndex, spanWinding);
+ } while (true);
+ } while (true);
+ return closable;
+}
+
+static Segment* findSortableTopNew(SkTDArray<Contour*>& contourList, bool& firstContour, int& index,
+ int& endIndex, SkPoint& topLeft, bool& unsortable) {
+ Segment* current;
+ bool first = true;
+ int contourWinding, oppContourWinding;
+ do {
+ current = findSortableTop(contourList, index, endIndex, topLeft, unsortable, true);
+ if (!current) {
+ if (first) {
+ return NULL;
+ }
+ break;
+ }
+ first = false;
+ if (firstContour) {
+ current->initWinding(index, endIndex, 0, 0);
+ firstContour = false;
+ return current;
+ }
+ int minIndex = SkMin32(index, endIndex);
+ int sumWinding = current->windSum(minIndex);
+ if (sumWinding == SK_MinS32) {
+ sumWinding = current->computeSum(index, endIndex, true);
+ if (sumWinding != SK_MinS32) {
+ return current;
+ }
+ }
+ contourWinding = innerContourCheck(contourList, current, index, endIndex, false);
+ if (contourWinding == SK_MinS32) {
+ continue;
+ }
+ oppContourWinding = innerContourCheck(contourList, current, index, endIndex, true);
+ if (oppContourWinding != SK_MinS32) {
+ break;
+ }
+ current->initWinding(index, endIndex, contourWinding, oppContourWinding);
+ return current;
+ } while (true);
+ if (!current) {
+ // the simple upward projection of the unresolved points hit unsortable angles
+ // shoot rays at right angles to the segment to find its winding, ignoring angle cases
+ int (*checker)(SkTDArray<Contour*>& contourList, double mid,
+ const Segment* current, int index, int endIndex, bool opp);
+ current = tryRightAngleRay(contourList, index, endIndex, topLeft, unsortable, checker);
+ contourWinding = rightAngleWinding(checker, contourList, current, index, endIndex, false);
+ oppContourWinding = rightAngleWinding(checker, contourList, current, index, endIndex, true);
+ }
+ current->initWinding(index, endIndex, contourWinding, oppContourWinding);
+ return current;
+}
+
+// rewrite that abandons keeping local track of winding
+static bool bridgeWindingX(SkTDArray<Contour*>& contourList, PathWrapper& simple) {
+ bool firstContour = true;
+ bool unsortable = false;
+ bool topUnsortable = false;
+ bool firstRetry = false;
+ SkPoint topLeft = {SK_ScalarMin, SK_ScalarMin};
+ do {
+ int index, endIndex;
+ Segment* current = findSortableTopNew(contourList, firstContour, index, endIndex, topLeft,
+ topUnsortable);
+ if (!current) {
+ if (topUnsortable) {
+ topUnsortable = false;
+ SkASSERT(!firstRetry);
+ firstRetry = true;
+ topLeft.fX = topLeft.fY = SK_ScalarMin;
+ continue;
+ }
+ break;
+ }
+ SkTDArray<Span*> chaseArray;
+ do {
+ if (current->activeWinding(index, endIndex)) {
+ do {
+ #if DEBUG_ACTIVE_SPANS
+ if (!unsortable && current->done()) {
+ debugShowActiveSpans(contourList);
+ }
+ #endif
+ SkASSERT(unsortable || !current->done());
+ int nextStart = index;
+ int nextEnd = endIndex;
+ Segment* next = current->findNextWinding(chaseArray, nextStart, nextEnd,
+ unsortable);
+ if (!next) {
+ if (!unsortable && simple.hasMove()
+ && current->verb() != SkPath::kLine_Verb
+ && !simple.isClosed()) {
+ current->addCurveTo(index, endIndex, simple, true);
+ SkASSERT(simple.isClosed());
+ }
+ break;
+ }
+ current->addCurveTo(index, endIndex, simple, true);
+ current = next;
+ index = nextStart;
+ endIndex = nextEnd;
+ } while (!simple.isClosed() && ((!unsortable) || !current->done()));
+ if (current->activeWinding(index, endIndex) && !simple.isClosed()) {
+ SkASSERT(unsortable);
+ int min = SkMin32(index, endIndex);
+ if (!current->done(min)) {
+ current->addCurveTo(index, endIndex, simple, true);
+ current->markDoneUnary(min);
+ }
+ }
+ simple.close();
+ } else {
+ Span* last = current->markAndChaseDoneBinary(index, endIndex);
+ if (last) {
+ *chaseArray.append() = last;
+ }
+ }
+ current = findChase(chaseArray, index, endIndex);
+ #if DEBUG_ACTIVE_SPANS
+ debugShowActiveSpans(contourList);
+ #endif
+ if (!current) {
+ break;
+ }
+ } while (true);
+ } while (true);
+ return simple.someAssemblyRequired();
+}
+
+// returns true if all edges were processed
+static bool bridgeXor(SkTDArray<Contour*>& contourList, PathWrapper& simple) {
+ Segment* current;
+ int start, end;
+ bool unsortable = false;
+ bool closable = true;
+ while ((current = findUndone(contourList, start, end))) {
+ do {
+ SkASSERT(unsortable || !current->done());
+ int nextStart = start;
+ int nextEnd = end;
+ Segment* next = current->findNextXor(nextStart, nextEnd, unsortable);
+ if (!next) {
+ if (!unsortable && simple.hasMove()
+ && current->verb() != SkPath::kLine_Verb
+ && !simple.isClosed()) {
+ current->addCurveTo(start, end, simple, true);
+ SkASSERT(simple.isClosed());
+ }
+ break;
+ }
+ #if DEBUG_FLOW
+ SkDebugf("%s current id=%d from=(%1.9g,%1.9g) to=(%1.9g,%1.9g)\n", __FUNCTION__,
+ current->debugID(), current->xyAtT(start).fX, current->xyAtT(start).fY,
+ current->xyAtT(end).fX, current->xyAtT(end).fY);
+ #endif
+ current->addCurveTo(start, end, simple, true);
+ current = next;
+ start = nextStart;
+ end = nextEnd;
+ } while (!simple.isClosed() && (!unsortable || !current->done()));
+ if (!simple.isClosed()) {
+ SkASSERT(unsortable);
+ int min = SkMin32(start, end);
+ if (!current->done(min)) {
+ current->addCurveTo(start, end, simple, true);
+ current->markDone(min, 1);
+ }
+ closable = false;
+ }
+ simple.close();
+ #if DEBUG_ACTIVE_SPANS
+ debugShowActiveSpans(contourList);
+ #endif
+ }
+ return closable;
+}
+
+static void fixOtherTIndex(SkTDArray<Contour*>& contourList) {
+ int contourCount = contourList.count();
+ for (int cTest = 0; cTest < contourCount; ++cTest) {
+ Contour* contour = contourList[cTest];
+ contour->fixOtherTIndex();
+ }
+}
+
+static void sortSegments(SkTDArray<Contour*>& contourList) {
+ int contourCount = contourList.count();
+ for (int cTest = 0; cTest < contourCount; ++cTest) {
+ Contour* contour = contourList[cTest];
+ contour->sortSegments();
+ }
+}
+
+static void makeContourList(SkTArray<Contour>& contours, SkTDArray<Contour*>& list,
+ bool evenOdd, bool oppEvenOdd) {
+ int count = contours.count();
+ if (count == 0) {
+ return;
+ }
+ for (int index = 0; index < count; ++index) {
+ Contour& contour = contours[index];
+ contour.setOppXor(contour.operand() ? evenOdd : oppEvenOdd);
+ *list.append() = &contour;
+ }
+ QSort<Contour>(list.begin(), list.end() - 1);
+}
+
+static bool approximatelyEqual(const SkPoint& a, const SkPoint& b) {
+ return AlmostEqualUlps(a.fX, b.fX) && AlmostEqualUlps(a.fY, b.fY);
+}
+
+ /*
+ check start and end of each contour
+ if not the same, record them
+ match them up
+ connect closest
+ reassemble contour pieces into new path
+ */
+static void assemble(const PathWrapper& path, PathWrapper& simple) {
+#if DEBUG_PATH_CONSTRUCTION
+ SkDebugf("%s\n", __FUNCTION__);
+#endif
+ SkTArray<Contour> contours;
+ EdgeBuilder builder(path, contours);
+ builder.finish();
+ int count = contours.count();
+ int outer;
+ SkTDArray<int> runs; // indices of partial contours
+ for (outer = 0; outer < count; ++outer) {
+ const Contour& eContour = contours[outer];
+ const SkPoint& eStart = eContour.start();
+ const SkPoint& eEnd = eContour.end();
+#if DEBUG_ASSEMBLE
+ SkDebugf("%s contour", __FUNCTION__);
+ if (!approximatelyEqual(eStart, eEnd)) {
+ SkDebugf("[%d]", runs.count());
+ } else {
+ SkDebugf(" ");
+ }
+ SkDebugf(" start=(%1.9g,%1.9g) end=(%1.9g,%1.9g)\n",
+ eStart.fX, eStart.fY, eEnd.fX, eEnd.fY);
+#endif
+ if (approximatelyEqual(eStart, eEnd)) {
+ eContour.toPath(simple);
+ continue;
+ }
+ *runs.append() = outer;
+ }
+ count = runs.count();
+ if (count == 0) {
+ return;
+ }
+ SkTDArray<int> sLink, eLink;
+ sLink.setCount(count);
+ eLink.setCount(count);
+ SkTDArray<double> sBest, eBest;
+ sBest.setCount(count);
+ eBest.setCount(count);
+ int rIndex;
+ for (rIndex = 0; rIndex < count; ++rIndex) {
+ outer = runs[rIndex];
+ const Contour& oContour = contours[outer];
+ const SkPoint& oStart = oContour.start();
+ const SkPoint& oEnd = oContour.end();
+ double dx = oEnd.fX - oStart.fX;
+ double dy = oEnd.fY - oStart.fY;
+ double dist = dx * dx + dy * dy;
+ sBest[rIndex] = eBest[rIndex] = dist;
+ sLink[rIndex] = eLink[rIndex] = rIndex;
+ }
+ for (rIndex = 0; rIndex < count - 1; ++rIndex) {
+ outer = runs[rIndex];
+ const Contour& oContour = contours[outer];
+ const SkPoint& oStart = oContour.start();
+ const SkPoint& oEnd = oContour.end();
+ double bestStartDist = sBest[rIndex];
+ double bestEndDist = eBest[rIndex];
+ for (int iIndex = rIndex + 1; iIndex < count; ++iIndex) {
+ int inner = runs[iIndex];
+ const Contour& iContour = contours[inner];
+ const SkPoint& iStart = iContour.start();
+ const SkPoint& iEnd = iContour.end();
+ double dx = iStart.fX - oStart.fX;
+ double dy = iStart.fY - oStart.fY;
+ double dist = dx * dx + dy * dy;
+ if (bestStartDist > dist && sBest[iIndex] > dist) {
+ sBest[iIndex] = bestStartDist = dist;
+ sLink[rIndex] = ~iIndex;
+ sLink[iIndex] = ~rIndex;
+ }
+ dx = iEnd.fX - oStart.fX;
+ dy = iEnd.fY - oStart.fY;
+ dist = dx * dx + dy * dy;
+ if (bestStartDist > dist && eBest[iIndex] > dist) {
+ eBest[iIndex] = bestStartDist = dist;
+ sLink[rIndex] = iIndex;
+ eLink[iIndex] = rIndex;
+ }
+ dx = iStart.fX - oEnd.fX;
+ dy = iStart.fY - oEnd.fY;
+ dist = dx * dx + dy * dy;
+ if (bestEndDist > dist && sBest[iIndex] > dist) {
+ sBest[iIndex] = bestEndDist = dist;
+ sLink[iIndex] = rIndex;
+ eLink[rIndex] = iIndex;
+ }
+ dx = iEnd.fX - oEnd.fX;
+ dy = iEnd.fY - oEnd.fY;
+ dist = dx * dx + dy * dy;
+ if (bestEndDist > dist && eBest[iIndex] > dist) {
+ eBest[iIndex] = bestEndDist = dist;
+ eLink[iIndex] = ~rIndex;
+ eLink[rIndex] = ~iIndex;
+ }
+ }
+ }
+#if DEBUG_ASSEMBLE
+ for (rIndex = 0; rIndex < count; ++rIndex) {
+ int s = sLink[rIndex];
+ int e = eLink[rIndex];
+ SkDebugf("%s %c%d <- s%d - e%d -> %c%d\n", __FUNCTION__, s < 0 ? 's' : 'e',
+ s < 0 ? ~s : s, rIndex, rIndex, e < 0 ? 'e' : 's', e < 0 ? ~e : e);
+ }
+#endif
+ rIndex = 0;
+ do {
+ bool forward = true;
+ bool first = true;
+ int sIndex = sLink[rIndex];
+ SkASSERT(sIndex != INT_MAX);
+ sLink[rIndex] = INT_MAX;
+ int eIndex;
+ if (sIndex < 0) {
+ eIndex = sLink[~sIndex];
+ sLink[~sIndex] = INT_MAX;
+ } else {
+ eIndex = eLink[sIndex];
+ eLink[sIndex] = INT_MAX;
+ }
+ SkASSERT(eIndex != INT_MAX);
+#if DEBUG_ASSEMBLE
+ SkDebugf("%s sIndex=%c%d eIndex=%c%d\n", __FUNCTION__, sIndex < 0 ? 's' : 'e',
+ sIndex < 0 ? ~sIndex : sIndex, eIndex < 0 ? 's' : 'e',
+ eIndex < 0 ? ~eIndex : eIndex);
+#endif
+ do {
+ outer = runs[rIndex];
+ const Contour& contour = contours[outer];
+ if (first) {
+ first = false;
+ const SkPoint* startPtr = &contour.start();
+ simple.deferredMove(startPtr[0]);
+ }
+ if (forward) {
+ contour.toPartialForward(simple);
+ } else {
+ contour.toPartialBackward(simple);
+ }
+#if DEBUG_ASSEMBLE
+ SkDebugf("%s rIndex=%d eIndex=%s%d close=%d\n", __FUNCTION__, rIndex,
+ eIndex < 0 ? "~" : "", eIndex < 0 ? ~eIndex : eIndex,
+ sIndex == ((rIndex != eIndex) ^ forward ? eIndex : ~eIndex));
+#endif
+ if (sIndex == ((rIndex != eIndex) ^ forward ? eIndex : ~eIndex)) {
+ simple.close();
+ break;
+ }
+ if (forward) {
+ eIndex = eLink[rIndex];
+ SkASSERT(eIndex != INT_MAX);
+ eLink[rIndex] = INT_MAX;
+ if (eIndex >= 0) {
+ SkASSERT(sLink[eIndex] == rIndex);
+ sLink[eIndex] = INT_MAX;
+ } else {
+ SkASSERT(eLink[~eIndex] == ~rIndex);
+ eLink[~eIndex] = INT_MAX;
+ }
+ } else {
+ eIndex = sLink[rIndex];
+ SkASSERT(eIndex != INT_MAX);
+ sLink[rIndex] = INT_MAX;
+ if (eIndex >= 0) {
+ SkASSERT(eLink[eIndex] == rIndex);
+ eLink[eIndex] = INT_MAX;
+ } else {
+ SkASSERT(sLink[~eIndex] == ~rIndex);
+ sLink[~eIndex] = INT_MAX;
+ }
+ }
+ rIndex = eIndex;
+ if (rIndex < 0) {
+ forward ^= 1;
+ rIndex = ~rIndex;
+ }
+ } while (true);
+ for (rIndex = 0; rIndex < count; ++rIndex) {
+ if (sLink[rIndex] != INT_MAX) {
+ break;
+ }
+ }
+ } while (rIndex < count);
+#if DEBUG_ASSEMBLE
+ for (rIndex = 0; rIndex < count; ++rIndex) {
+ SkASSERT(sLink[rIndex] == INT_MAX);
+ SkASSERT(eLink[rIndex] == INT_MAX);
+ }
+#endif
+}
+
+void simplifyx(const SkPath& path, SkPath& result) {
+ // returns 1 for evenodd, -1 for winding, regardless of inverse-ness
+ result.reset();
+ result.setFillType(SkPath::kEvenOdd_FillType);
+ PathWrapper simple(result);
+
+ // turn path into list of segments
+ SkTArray<Contour> contours;
+ // FIXME: add self-intersecting cubics' T values to segment
+ EdgeBuilder builder(path, contours);
+ builder.finish();
+ SkTDArray<Contour*> contourList;
+ makeContourList(contours, contourList, false, false);
+ Contour** currentPtr = contourList.begin();
+ if (!currentPtr) {
+ return;
+ }
+ Contour** listEnd = contourList.end();
+ // find all intersections between segments
+ do {
+ Contour** nextPtr = currentPtr;
+ Contour* current = *currentPtr++;
+ Contour* next;
+ do {
+ next = *nextPtr++;
+ } while (addIntersectTs(current, next) && nextPtr != listEnd);
+ } while (currentPtr != listEnd);
+ // eat through coincident edges
+ coincidenceCheck(contourList, 0);
+ fixOtherTIndex(contourList);
+ sortSegments(contourList);
+#if DEBUG_ACTIVE_SPANS
+ debugShowActiveSpans(contourList);
+#endif
+ // construct closed contours
+ if (builder.xorMask() == kWinding_Mask
+ ? gUseOldBridgeWinding ? !bridgeWinding(contourList, simple)
+ : bridgeWindingX(contourList, simple)
+ : !bridgeXor(contourList, simple))
+ { // if some edges could not be resolved, assemble remaining fragments
+ SkPath temp;
+ temp.setFillType(SkPath::kEvenOdd_FillType);
+ PathWrapper assembled(temp);
+ assemble(simple, assembled);
+ result = *assembled.nativePath();
+ }
+}
+
diff --git a/experimental/Intersection/SimplifyFindNext_Test.cpp b/experimental/Intersection/SimplifyFindNext_Test.cpp
new file mode 100644
index 0000000000..b01b951d23
--- /dev/null
+++ b/experimental/Intersection/SimplifyFindNext_Test.cpp
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#define DEBUG_TEST 1
+
+#include "Simplify.h"
+
+namespace SimplifyFindNextTest {
+
+#include "Simplify.cpp"
+
+} // end of SimplifyFindNextTest namespace
+
+#include "Intersection_Tests.h"
+
+static const SimplifyFindNextTest::Segment* testCommon(
+ int contourWinding, int spanWinding, int startIndex, int endIndex,
+ SkTArray<SimplifyFindNextTest::Contour>& contours) {
+ SkTDArray<SimplifyFindNextTest::Contour*> contourList;
+ makeContourList(contours, contourList, false, false);
+ addIntersectTs(contourList[0], contourList[0]);
+ if (contours.count() > 1) {
+ SkASSERT(contours.count() == 2);
+ addIntersectTs(contourList[0], contourList[1]);
+ addIntersectTs(contourList[1], contourList[1]);
+ }
+ fixOtherTIndex(contourList);
+ SimplifyFindNextTest::Segment& segment = contours[0].debugSegments()[0];
+ SkPoint pts[2];
+ pts[0] = segment.xyAtT(&segment.span(endIndex));
+ int nextStart = startIndex;
+ int nextEnd = endIndex;
+ SkTDArray<SimplifyFindNextTest::Span*> chaseArray;
+ bool unsortable = false;
+ SimplifyFindNextTest::Segment* next = segment.findNextWinding(chaseArray,
+ true, nextStart, nextEnd, contourWinding, spanWinding,
+ unsortable);
+ pts[1] = next->xyAtT(&next->span(nextStart));
+ SkASSERT(pts[0] == pts[1]);
+ return next;
+}
+
+static void test(const SkPath& path) {
+ SkTArray<SimplifyFindNextTest::Contour> contours;
+ SimplifyFindNextTest::EdgeBuilder builder(path, contours);
+ int contourWinding = 0;
+ int spanWinding = 1;
+ int start = 0;
+ int end = 1;
+ testCommon(contourWinding, spanWinding, start, end, contours);
+}
+
+static void test(const SkPath& path, int start, int end) {
+ SkTArray<SimplifyFindNextTest::Contour> contours;
+ SimplifyFindNextTest::EdgeBuilder builder(path, contours);
+ int contourWinding = 0;
+ int spanWinding = 1;
+ testCommon(contourWinding, spanWinding, start, end, contours);
+}
+
+static void testLine1() {
+ SkPath path;
+ path.moveTo(2,0);
+ path.lineTo(1,1);
+ path.lineTo(0,0);
+ path.close();
+ test(path);
+}
+
+static void addInnerCWTriangle(SkPath& path) {
+ path.moveTo(3,0);
+ path.lineTo(4,1);
+ path.lineTo(2,1);
+ path.close();
+}
+
+#if DEBUG_UNUSED
+static void addInnerCCWTriangle(SkPath& path) {
+ path.moveTo(3,0);
+ path.lineTo(2,1);
+ path.lineTo(4,1);
+ path.close();
+}
+#endif
+
+static void addOuterCWTriangle(SkPath& path) {
+ path.moveTo(3,0);
+ path.lineTo(6,2);
+ path.lineTo(0,2);
+ path.close();
+}
+
+#if DEBUG_UNUSED
+static void addOuterCCWTriangle(SkPath& path) {
+ path.moveTo(3,0);
+ path.lineTo(0,2);
+ path.lineTo(6,2);
+ path.close();
+}
+#endif
+
+static void testLine2() {
+ SkPath path;
+ addInnerCWTriangle(path);
+ addOuterCWTriangle(path);
+ test(path, 0, 3);
+}
+
+static void testLine3() {
+ SkPath path;
+ addInnerCWTriangle(path);
+ addOuterCWTriangle(path);
+ test(path, 3, 0);
+}
+
+static void testLine4() {
+ SkPath path;
+ addInnerCWTriangle(path);
+ addOuterCWTriangle(path);
+ test(path, 3, 2);
+}
+
+static void (*tests[])() = {
+ testLine1,
+ testLine2,
+ testLine3,
+ testLine4,
+};
+
+static const size_t testCount = sizeof(tests) / sizeof(tests[0]);
+
+static void (*firstTest)() = 0;
+static bool skipAll = false;
+
+void SimplifyFindNext_Test() {
+ if (skipAll) {
+ return;
+ }
+ size_t index = 0;
+ if (firstTest) {
+ while (index < testCount && tests[index] != firstTest) {
+ ++index;
+ }
+ }
+ bool firstTestComplete = false;
+ for ( ; index < testCount; ++index) {
+ (*tests[index])();
+ firstTestComplete = true;
+ }
+}
diff --git a/experimental/Intersection/SimplifyFindTop_Test.cpp b/experimental/Intersection/SimplifyFindTop_Test.cpp
new file mode 100644
index 0000000000..d29325cebd
--- /dev/null
+++ b/experimental/Intersection/SimplifyFindTop_Test.cpp
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "Simplify.h"
+
+namespace SimplifyFindTopTest {
+
+#include "Simplify.cpp"
+
+} // end of SimplifyFindTopTest namespace
+
+#include "Intersection_Tests.h"
+
+static const SimplifyFindTopTest::Segment* testCommon(
+ SkTArray<SimplifyFindTopTest::Contour>& contours,
+ int& index, int& end) {
+ SkTDArray<SimplifyFindTopTest::Contour*> contourList;
+ makeContourList(contours, contourList, false, false);
+ addIntersectTs(contourList[0], contourList[0]);
+ if (contours.count() > 1) {
+ SkASSERT(contours.count() == 2);
+ addIntersectTs(contourList[0], contourList[1]);
+ addIntersectTs(contourList[1], contourList[1]);
+ }
+ fixOtherTIndex(contourList);
+#if SORTABLE_CONTOURS // old way
+ SimplifyFindTopTest::Segment* topStart = findTopContour(contourList);
+ const SimplifyFindTopTest::Segment* topSegment = topStart->findTop(index,
+ end);
+#else
+ SkPoint bestXY = {SK_ScalarMin, SK_ScalarMin};
+ bool unsortable = false;
+ const SimplifyFindTopTest::Segment* topSegment =
+ findSortableTop(contourList, index, end, bestXY, unsortable, true);
+#endif
+ return topSegment;
+}
+
+static void test(const SkPath& path) {
+ SkTArray<SimplifyFindTopTest::Contour> contours;
+ SimplifyFindTopTest::EdgeBuilder builder(path, contours);
+ int index, end;
+ testCommon(contours, index, end);
+ SkASSERT(index + 1 == end);
+}
+
+static void test(const SkPath& path, SkScalar x1, SkScalar y1,
+ SkScalar x2, SkScalar y2) {
+ SkTArray<SimplifyFindTopTest::Contour> contours;
+ SimplifyFindTopTest::EdgeBuilder builder(path, contours);
+ int index, end;
+ const SimplifyFindTopTest::Segment* topSegment =
+ testCommon(contours, index, end);
+ SkPoint pts[2];
+ double firstT = topSegment->t(index);
+ pts[0] = topSegment->xyAtT(&topSegment->span(index));
+ int direction = index < end ? 1 : -1;
+ do {
+ index += direction;
+ double nextT = topSegment->t(index);
+ if (nextT == firstT) {
+ continue;
+ }
+ pts[1] = topSegment->xyAtT(&topSegment->span(index));
+ if (pts[0] != pts[1]) {
+ break;
+ }
+ } while (true);
+ SkASSERT(pts[0].fX == x1);
+ SkASSERT(pts[0].fY == y1);
+ SkASSERT(pts[1].fX == x2);
+ SkASSERT(pts[1].fY == y2);
+}
+
+static void testLine1() {
+ SkPath path;
+ path.moveTo(2,0);
+ path.lineTo(1,1);
+ path.lineTo(0,0);
+ path.close();
+ test(path);
+}
+
+static void addInnerCWTriangle(SkPath& path) {
+ path.moveTo(3,0);
+ path.lineTo(4,1);
+ path.lineTo(2,1);
+ path.close();
+}
+
+static void addInnerCCWTriangle(SkPath& path) {
+ path.moveTo(3,0);
+ path.lineTo(2,1);
+ path.lineTo(4,1);
+ path.close();
+}
+
+static void addOuterCWTriangle(SkPath& path) {
+ path.moveTo(3,0);
+ path.lineTo(6,2);
+ path.lineTo(0,2);
+ path.close();
+}
+
+static void addOuterCCWTriangle(SkPath& path) {
+ path.moveTo(3,0);
+ path.lineTo(0,2);
+ path.lineTo(6,2);
+ path.close();
+}
+
+static void testLine2() {
+ SkPath path;
+ addInnerCWTriangle(path);
+ addOuterCWTriangle(path);
+ test(path, 0, 2, 3, 0);
+}
+
+static void testLine3() {
+ SkPath path;
+ addOuterCWTriangle(path);
+ addInnerCWTriangle(path);
+ test(path, 0, 2, 3, 0);
+}
+
+static void testLine4() {
+ SkPath path;
+ addInnerCCWTriangle(path);
+ addOuterCWTriangle(path);
+ test(path, 0, 2, 3, 0);
+}
+
+static void testLine5() {
+ SkPath path;
+ addOuterCWTriangle(path);
+ addInnerCCWTriangle(path);
+ test(path, 0, 2, 3, 0);
+}
+
+static void testLine6() {
+ SkPath path;
+ addInnerCWTriangle(path);
+ addOuterCCWTriangle(path);
+ test(path, 0, 2, 3, 0);
+}
+
+static void testLine7() {
+ SkPath path;
+ addOuterCCWTriangle(path);
+ addInnerCWTriangle(path);
+ test(path, 0, 2, 3, 0);
+}
+
+static void testLine8() {
+ SkPath path;
+ addInnerCCWTriangle(path);
+ addOuterCCWTriangle(path);
+ test(path, 0, 2, 3, 0);
+}
+
+static void testLine9() {
+ SkPath path;
+ addOuterCCWTriangle(path);
+ addInnerCCWTriangle(path);
+ test(path, 0, 2, 3, 0);
+}
+
+static void testQuads() {
+ SkPath path;
+ path.moveTo(2,0);
+ path.quadTo(1,1, 0,0);
+ path.close();
+ test(path);
+}
+
+static void testCubics() {
+ SkPath path;
+ path.moveTo(2,0);
+ path.cubicTo(2,3, 1,1, 0,0);
+ path.close();
+ test(path);
+}
+
+static void (*tests[])() = {
+ testLine1,
+ testLine2,
+ testLine3,
+ testLine4,
+ testLine5,
+ testLine6,
+ testLine7,
+ testLine8,
+ testLine9,
+ testQuads,
+ testCubics
+};
+
+static const size_t testCount = sizeof(tests) / sizeof(tests[0]);
+
+static void (*firstTest)() = 0;
+static bool skipAll = false;
+
+void SimplifyFindTop_Test() {
+ if (skipAll) {
+ return;
+ }
+ size_t index = 0;
+ if (firstTest) {
+ while (index < testCount && tests[index] != firstTest) {
+ ++index;
+ }
+ }
+ bool firstTestComplete = false;
+ for ( ; index < testCount; ++index) {
+ (*tests[index])();
+ firstTestComplete = true;
+ }
+}
diff --git a/experimental/Intersection/SimplifyNew_Test.cpp b/experimental/Intersection/SimplifyNew_Test.cpp
new file mode 100644
index 0000000000..7a1fc12a67
--- /dev/null
+++ b/experimental/Intersection/SimplifyNew_Test.cpp
@@ -0,0 +1,3616 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "EdgeWalker_Test.h"
+#include "Intersection_Tests.h"
+#include "ShapeOps.h"
+
+#define TEST(name) { name, #name }
+
+static void testLine1() {
+ SkPath path;
+ path.moveTo(2,0);
+ path.lineTo(1,1);
+ path.lineTo(0,0);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testLine1x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(2,0);
+ path.lineTo(1,1);
+ path.lineTo(0,0);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void addInnerCWTriangle(SkPath& path) {
+ path.moveTo(3,0);
+ path.lineTo(4,1);
+ path.lineTo(2,1);
+ path.close();
+}
+
+static void addInnerCCWTriangle(SkPath& path) {
+ path.moveTo(3,0);
+ path.lineTo(2,1);
+ path.lineTo(4,1);
+ path.close();
+}
+
+static void addOuterCWTriangle(SkPath& path) {
+ path.moveTo(3,0);
+ path.lineTo(6,2);
+ path.lineTo(0,2);
+ path.close();
+}
+
+static void addOuterCCWTriangle(SkPath& path) {
+ path.moveTo(3,0);
+ path.lineTo(0,2);
+ path.lineTo(6,2);
+ path.close();
+}
+
+static void testLine2() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ addInnerCWTriangle(path);
+ addOuterCWTriangle(path);
+ testSimplifyx(path);
+}
+
+static void testLine2x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ addInnerCWTriangle(path);
+ addOuterCWTriangle(path);
+ testSimplifyx(path);
+}
+
+static void testLine3() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ addInnerCCWTriangle(path);
+ addOuterCWTriangle(path);
+ testSimplifyx(path);
+}
+
+static void testLine3x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ addInnerCCWTriangle(path);
+ addOuterCWTriangle(path);
+ testSimplifyx(path);
+}
+
+static void testLine3a() {
+ SkPath path;
+ addInnerCWTriangle(path);
+ addOuterCCWTriangle(path);
+ testSimplifyx(path);
+}
+
+static void testLine3ax() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ addInnerCWTriangle(path);
+ addOuterCCWTriangle(path);
+ testSimplifyx(path);
+}
+
+static void testLine3b() {
+ SkPath path;
+ addInnerCCWTriangle(path);
+ addOuterCCWTriangle(path);
+ testSimplifyx(path);
+}
+
+static void testLine3bx() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ addInnerCCWTriangle(path);
+ addOuterCCWTriangle(path);
+ testSimplifyx(path);
+}
+
+static void testLine4() {
+ SkPath path;
+ addOuterCCWTriangle(path);
+ addOuterCWTriangle(path);
+ testSimplifyx(path);
+}
+
+static void testLine4x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ addOuterCCWTriangle(path);
+ addOuterCWTriangle(path);
+ testSimplifyx(path);
+}
+
+static void testLine5() {
+ SkPath path;
+ addOuterCWTriangle(path);
+ addOuterCWTriangle(path);
+ testSimplifyx(path);
+}
+
+static void testLine5x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ addOuterCWTriangle(path);
+ addOuterCWTriangle(path);
+ testSimplifyx(path);
+}
+
+static void testLine6() {
+ SkPath path;
+ path.moveTo(0,0);
+ path.lineTo(4,0);
+ path.lineTo(2,2);
+ path.close();
+ path.moveTo(2,0);
+ path.lineTo(6,0);
+ path.lineTo(4,2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testLine6x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0,0);
+ path.lineTo(4,0);
+ path.lineTo(2,2);
+ path.close();
+ path.moveTo(2,0);
+ path.lineTo(6,0);
+ path.lineTo(4,2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testLine7() {
+ SkPath path;
+ path.moveTo(0,0);
+ path.lineTo(4,0);
+ path.lineTo(2,2);
+ path.close();
+ path.moveTo(6,0);
+ path.lineTo(2,0);
+ path.lineTo(4,2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testLine7x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0,0);
+ path.lineTo(4,0);
+ path.lineTo(2,2);
+ path.close();
+ path.moveTo(6,0);
+ path.lineTo(2,0);
+ path.lineTo(4,2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testLine7a() {
+ SkPath path;
+ path.moveTo(0,0);
+ path.lineTo(4,0);
+ path.lineTo(2,2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testLine7ax() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0,0);
+ path.lineTo(4,0);
+ path.lineTo(2,2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testLine7b() {
+ SkPath path;
+ path.moveTo(0,0);
+ path.lineTo(4,0);
+ path.close();
+ path.moveTo(6,0);
+ path.lineTo(2,0);
+ path.lineTo(4,2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testLine7bx() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0,0);
+ path.lineTo(4,0);
+ path.close();
+ path.moveTo(6,0);
+ path.lineTo(2,0);
+ path.lineTo(4,2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testLine8() {
+ SkPath path;
+ path.moveTo(0,4);
+ path.lineTo(4,4);
+ path.lineTo(2,2);
+ path.close();
+ path.moveTo(2,4);
+ path.lineTo(6,4);
+ path.lineTo(4,2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testLine8x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0,4);
+ path.lineTo(4,4);
+ path.lineTo(2,2);
+ path.close();
+ path.moveTo(2,4);
+ path.lineTo(6,4);
+ path.lineTo(4,2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testLine9() {
+ SkPath path;
+ path.moveTo(0,4);
+ path.lineTo(4,4);
+ path.lineTo(2,2);
+ path.close();
+ path.moveTo(6,4);
+ path.lineTo(2,4);
+ path.lineTo(4,2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testLine9x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0,4);
+ path.lineTo(4,4);
+ path.lineTo(2,2);
+ path.close();
+ path.moveTo(6,4);
+ path.lineTo(2,4);
+ path.lineTo(4,2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testLine10() {
+ SkPath path;
+ path.moveTo(0,4);
+ path.lineTo(4,4);
+ path.lineTo(2,2);
+ path.close();
+ path.moveTo(2,1);
+ path.lineTo(3,4);
+ path.lineTo(6,1);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testLine10x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0,4);
+ path.lineTo(4,4);
+ path.lineTo(2,2);
+ path.close();
+ path.moveTo(2,1);
+ path.lineTo(3,4);
+ path.lineTo(6,1);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testLine10a() {
+ SkPath path;
+ path.moveTo(0,4);
+ path.lineTo(8,4);
+ path.lineTo(4,0);
+ path.close();
+ path.moveTo(2,2);
+ path.lineTo(3,3);
+ path.lineTo(4,2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testLine10ax() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0,4);
+ path.lineTo(8,4);
+ path.lineTo(4,0);
+ path.close();
+ path.moveTo(2,2);
+ path.lineTo(3,3);
+ path.lineTo(4,2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void addCWContainer(SkPath& path) {
+ path.moveTo(6,4);
+ path.lineTo(0,4);
+ path.lineTo(3,1);
+ path.close();
+}
+
+static void addCCWContainer(SkPath& path) {
+ path.moveTo(0,4);
+ path.lineTo(6,4);
+ path.lineTo(3,1);
+ path.close();
+}
+
+static void addCWContents(SkPath& path) {
+ path.moveTo(2,3);
+ path.lineTo(3,2);
+ path.lineTo(4,3);
+ path.close();
+}
+
+static void addCCWContents(SkPath& path) {
+ path.moveTo(3,2);
+ path.lineTo(2,3);
+ path.lineTo(4,3);
+ path.close();
+}
+
+static void testLine11() {
+ SkPath path;
+ addCWContainer(path);
+ addCWContents(path);
+ testSimplifyx(path);
+}
+
+static void testLine11x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ addCWContainer(path);
+ addCWContents(path);
+ testSimplifyx(path);
+}
+
+static void testLine12() {
+ SkPath path;
+ addCCWContainer(path);
+ addCWContents(path);
+ testSimplifyx(path);
+}
+
+static void testLine12x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ addCCWContainer(path);
+ addCWContents(path);
+ testSimplifyx(path);
+}
+
+static void testLine13() {
+ SkPath path;
+ addCWContainer(path);
+ addCCWContents(path);
+ testSimplifyx(path);
+}
+
+static void testLine13x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ addCWContainer(path);
+ addCCWContents(path);
+ testSimplifyx(path);
+}
+
+static void testLine14() {
+ SkPath path;
+ addCCWContainer(path);
+ addCCWContents(path);
+ testSimplifyx(path);
+}
+
+static void testLine14x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ addCCWContainer(path);
+ addCCWContents(path);
+ testSimplifyx(path);
+}
+
+static void testLine15() {
+ SkPath path;
+ path.addRect(0, 0, 9, 9, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine15x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 9, 9, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine16() {
+ SkPath path;
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(0, 4, 9, 9, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine16x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(0, 4, 9, 9, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine17() {
+ SkPath path;
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 12, 13, 13, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine17x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 12, 13, 13, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine18() {
+ SkPath path;
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(12, 4, 21, 21, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine18x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(12, 4, 21, 21, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine19() {
+ SkPath path;
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(12, 16, 21, 21, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine19x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(12, 16, 21, 21, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine20() {
+ SkPath path;
+ path.addRect(0, 12, 12, 12, SkPath::kCW_Direction);
+ path.addRect(0, 12, 9, 9, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine20x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 12, 12, 12, SkPath::kCW_Direction);
+ path.addRect(0, 12, 9, 9, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine21() {
+ SkPath path;
+ path.addRect(0, 12, 12, 12, SkPath::kCW_Direction);
+ path.addRect(0, 16, 9, 9, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine21x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 12, 12, 12, SkPath::kCW_Direction);
+ path.addRect(0, 16, 9, 9, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine22() {
+ SkPath path;
+ path.addRect(0, 12, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 12, 13, 13, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine22x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 12, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 12, 13, 13, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine23() {
+ SkPath path;
+ path.addRect(0, 12, 12, 12, SkPath::kCW_Direction);
+ path.addRect(12, 0, 21, 21, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine23x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 12, 12, 12, SkPath::kCW_Direction);
+ path.addRect(12, 0, 21, 21, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine24a() {
+ SkPath path;
+ path.moveTo(2,0);
+ path.lineTo(4,4);
+ path.lineTo(0,4);
+ path.close();
+ path.moveTo(2,0);
+ path.lineTo(1,2);
+ path.lineTo(2,2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testLine24ax() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(2,0);
+ path.lineTo(4,4);
+ path.lineTo(0,4);
+ path.close();
+ path.moveTo(2,0);
+ path.lineTo(1,2);
+ path.lineTo(2,2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testLine24() {
+ SkPath path;
+ path.addRect(0, 18, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 12, 13, 13, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine24x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 18, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 12, 13, 13, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine25() {
+ SkPath path;
+ path.addRect(0, 6, 12, 12, SkPath::kCW_Direction);
+ path.addRect(12, 0, 21, 21, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine25x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 6, 12, 12, SkPath::kCW_Direction);
+ path.addRect(12, 0, 21, 21, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine26() {
+ SkPath path;
+ path.addRect(0, 18, 12, 12, SkPath::kCW_Direction);
+ path.addRect(0, 12, 9, 9, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine26x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 18, 12, 12, SkPath::kCW_Direction);
+ path.addRect(0, 12, 9, 9, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine27() {
+ SkPath path;
+ path.addRect(0, 18, 12, 12, SkPath::kCW_Direction);
+ path.addRect(12, 8, 21, 21, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine27x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 18, 12, 12, SkPath::kCW_Direction);
+ path.addRect(12, 8, 21, 21, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine28() {
+ SkPath path;
+ path.addRect(0, 6, 12, 12, SkPath::kCW_Direction);
+ path.addRect(0, 0, 9, 9, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine28x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 6, 12, 12, SkPath::kCW_Direction);
+ path.addRect(0, 0, 9, 9, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine29() {
+ SkPath path;
+ path.addRect(0, 18, 12, 12, SkPath::kCW_Direction);
+ path.addRect(12, 12, 21, 21, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine29x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 18, 12, 12, SkPath::kCW_Direction);
+ path.addRect(12, 12, 21, 21, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine30() {
+ SkPath path;
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 4, 13, 13, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine30x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 4, 13, 13, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine31() {
+ SkPath path;
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(0, 4, 9, 9, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine31x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(0, 4, 9, 9, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine32() {
+ SkPath path;
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 12, 13, 13, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine32x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 12, 13, 13, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine33() {
+ SkPath path;
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 16, 13, 13, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine33x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 16, 13, 13, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine34() {
+ SkPath path;
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 6, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 12, 13, 13, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine34x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 6, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 12, 13, 13, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine35() {
+ SkPath path;
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(6, 0, 18, 18, SkPath::kCW_Direction);
+ path.addRect(4, 16, 13, 13, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine35x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(6, 0, 18, 18, SkPath::kCW_Direction);
+ path.addRect(4, 16, 13, 13, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine36() {
+ SkPath path;
+ path.addRect(0, 10, 20, 20, SkPath::kCW_Direction);
+ path.addRect(6, 12, 18, 18, SkPath::kCW_Direction);
+ path.addRect(4, 16, 13, 13, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine36x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 10, 20, 20, SkPath::kCW_Direction);
+ path.addRect(6, 12, 18, 18, SkPath::kCW_Direction);
+ path.addRect(4, 16, 13, 13, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine37() {
+ SkPath path;
+ path.addRect(0, 20, 20, 20, SkPath::kCW_Direction);
+ path.addRect(18, 24, 30, 30, SkPath::kCW_Direction);
+ path.addRect(0, 0, 9, 9, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine37x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 20, 20, 20, SkPath::kCW_Direction);
+ path.addRect(18, 24, 30, 30, SkPath::kCW_Direction);
+ path.addRect(0, 0, 9, 9, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine38() {
+ SkPath path;
+ path.addRect(10, 0, 30, 30, SkPath::kCW_Direction);
+ path.addRect(6, 12, 18, 18, SkPath::kCW_Direction);
+ path.addRect(12, 12, 21, 21, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine38x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(10, 0, 30, 30, SkPath::kCW_Direction);
+ path.addRect(6, 12, 18, 18, SkPath::kCW_Direction);
+ path.addRect(12, 12, 21, 21, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine40() {
+ SkPath path;
+ path.addRect(10, 0, 30, 30, SkPath::kCW_Direction);
+ path.addRect(12, 18, 24, 24, SkPath::kCW_Direction);
+ path.addRect(4, 16, 13, 13, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine40x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(10, 0, 30, 30, SkPath::kCW_Direction);
+ path.addRect(12, 18, 24, 24, SkPath::kCW_Direction);
+ path.addRect(4, 16, 13, 13, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine41() {
+ SkPath path;
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(18, 24, 30, 30, SkPath::kCW_Direction);
+ path.addRect(12, 0, 21, 21, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine41x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(18, 24, 30, 30, SkPath::kCW_Direction);
+ path.addRect(12, 0, 21, 21, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine42() {
+ SkPath path;
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(8, 16, 17, 17, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine42x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(8, 16, 17, 17, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine43() {
+ SkPath path;
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(6, 24, 18, 18, SkPath::kCW_Direction);
+ path.addRect(0, 32, 9, 36, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine43x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(6, 24, 18, 18, SkPath::kCW_Direction);
+ path.addRect(0, 32, 9, 36, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine44() {
+ SkPath path;
+ path.addRect(10, 40, 30, 30, SkPath::kCW_Direction);
+ path.addRect(18, 0, 30, 30, SkPath::kCW_Direction);
+ path.addRect(18, 32, 27, 36, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine44x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(10, 40, 30, 30, SkPath::kCW_Direction);
+ path.addRect(18, 0, 30, 30, SkPath::kCW_Direction);
+ path.addRect(18, 32, 27, 36, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine45() {
+ SkPath path;
+ path.addRect(10, 0, 30, 30, SkPath::kCW_Direction);
+ path.addRect(18, 0, 30, 30, SkPath::kCW_Direction);
+ path.addRect(24, 32, 33, 36, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine45x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(10, 0, 30, 30, SkPath::kCW_Direction);
+ path.addRect(18, 0, 30, 30, SkPath::kCW_Direction);
+ path.addRect(24, 32, 33, 36, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine46() {
+ SkPath path;
+ path.addRect(10, 40, 30, 30, SkPath::kCW_Direction);
+ path.addRect(24, 0, 36, 36, SkPath::kCW_Direction);
+ path.addRect(24, 32, 33, 36, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine46x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(10, 40, 30, 30, SkPath::kCW_Direction);
+ path.addRect(24, 0, 36, 36, SkPath::kCW_Direction);
+ path.addRect(24, 32, 33, 36, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine47() {
+ SkPath path;
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(0, 0, 9, 9, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine47x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(0, 0, 9, 9, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine48() {
+ SkPath path;
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 6, 12, 12, SkPath::kCW_Direction);
+ path.addRect(0, 0, 9, 9, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine48x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 6, 12, 12, SkPath::kCW_Direction);
+ path.addRect(0, 0, 9, 9, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine49() {
+ SkPath path;
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(0, 0, 9, 9, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine49x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(0, 0, 9, 9, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine50() {
+ SkPath path;
+ path.addRect(10, 30, 30, 30, SkPath::kCW_Direction);
+ path.addRect(24, 20, 36, 30, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine50x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(10, 30, 30, 30, SkPath::kCW_Direction);
+ path.addRect(24, 20, 36, 30, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine51() {
+ SkPath path;
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 12, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 12, 13, 13, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine51x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 12, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 12, 13, 13, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine52() {
+ SkPath path;
+ path.addRect(0, 30, 20, 20, SkPath::kCW_Direction);
+ path.addRect(6, 20, 18, 30, SkPath::kCW_Direction);
+ path.addRect(32, 0, 36, 41, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine52x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 30, 20, 20, SkPath::kCW_Direction);
+ path.addRect(6, 20, 18, 30, SkPath::kCW_Direction);
+ path.addRect(32, 0, 36, 41, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine53() {
+ SkPath path;
+ path.addRect(10, 30, 30, 30, SkPath::kCW_Direction);
+ path.addRect(12, 20, 24, 30, SkPath::kCW_Direction);
+ path.addRect(12, 32, 21, 36, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine53x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(10, 30, 30, 30, SkPath::kCW_Direction);
+ path.addRect(12, 20, 24, 30, SkPath::kCW_Direction);
+ path.addRect(12, 32, 21, 36, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine54() {
+ SkPath path;
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(6, 0, 18, 18, SkPath::kCW_Direction);
+ path.addRect(8, 4, 17, 17, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine54x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(6, 0, 18, 18, SkPath::kCW_Direction);
+ path.addRect(8, 4, 17, 17, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine55() {
+ SkPath path;
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(6, 6, 18, 18, SkPath::kCW_Direction);
+ path.addRect(4, 4, 13, 13, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine55x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(6, 6, 18, 18, SkPath::kCW_Direction);
+ path.addRect(4, 4, 13, 13, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine56() {
+ SkPath path;
+ path.addRect(0, 20, 20, 20, SkPath::kCW_Direction);
+ path.addRect(18, 20, 30, 30, SkPath::kCW_Direction);
+ path.addRect(12, 0, 21, 21, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine56x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 20, 20, 20, SkPath::kCW_Direction);
+ path.addRect(18, 20, 30, 30, SkPath::kCW_Direction);
+ path.addRect(12, 0, 21, 21, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine57() {
+ SkPath path;
+ path.addRect(20, 0, 40, 40, SkPath::kCW_Direction);
+ path.addRect(20, 0, 30, 40, SkPath::kCW_Direction);
+ path.addRect(12, 0, 21, 21, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine57x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(20, 0, 40, 40, SkPath::kCW_Direction);
+ path.addRect(20, 0, 30, 40, SkPath::kCW_Direction);
+ path.addRect(12, 0, 21, 21, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine58() {
+ SkPath path;
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 0, 12, 12, SkPath::kCCW_Direction);
+ path.addRect(0, 12, 9, 9, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine58x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 0, 12, 12, SkPath::kCCW_Direction);
+ path.addRect(0, 12, 9, 9, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine59() {
+ SkPath path;
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(6, 6, 18, 18, SkPath::kCCW_Direction);
+ path.addRect(4, 4, 13, 13, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine59x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(6, 6, 18, 18, SkPath::kCCW_Direction);
+ path.addRect(4, 4, 13, 13, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine60() {
+ SkPath path;
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(6, 12, 18, 18, SkPath::kCCW_Direction);
+ path.addRect(4, 12, 13, 13, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine60x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(6, 12, 18, 18, SkPath::kCCW_Direction);
+ path.addRect(4, 12, 13, 13, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine61() {
+ SkPath path;
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(12, 0, 24, 24, SkPath::kCCW_Direction);
+ path.addRect(12, 0, 21, 21, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine61x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(12, 0, 24, 24, SkPath::kCCW_Direction);
+ path.addRect(12, 0, 21, 21, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine62() {
+ SkPath path;
+ path.addRect(0, 0, 60, 60, SkPath::kCW_Direction);
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 12, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 12, 13, 13, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine62x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 60, 60, SkPath::kCW_Direction);
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 12, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 12, 13, 13, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine63() {
+ SkPath path;
+ path.addRect(0, 0, 60, 60, SkPath::kCW_Direction);
+ path.addRect(0, 10, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 6, 12, 12, SkPath::kCCW_Direction);
+ path.addRect(0, 32, 9, 36, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine63x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 60, 60, SkPath::kCW_Direction);
+ path.addRect(0, 10, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 6, 12, 12, SkPath::kCCW_Direction);
+ path.addRect(0, 32, 9, 36, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine64() {
+ SkPath path;
+ path.addRect(0, 0, 60, 60, SkPath::kCW_Direction);
+ path.addRect(10, 40, 30, 30, SkPath::kCW_Direction);
+ path.addRect(18, 6, 30, 30, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine64x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 60, 60, SkPath::kCW_Direction);
+ path.addRect(10, 40, 30, 30, SkPath::kCW_Direction);
+ path.addRect(18, 6, 30, 30, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine65() {
+ SkPath path;
+ path.addRect(0, 0, 60, 60, SkPath::kCW_Direction);
+ path.addRect(10, 0, 30, 30, SkPath::kCW_Direction);
+ path.addRect(24, 0, 36, 36, SkPath::kCW_Direction);
+ path.addRect(32, 6, 36, 41, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine65x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 60, 60, SkPath::kCW_Direction);
+ path.addRect(10, 0, 30, 30, SkPath::kCW_Direction);
+ path.addRect(24, 0, 36, 36, SkPath::kCW_Direction);
+ path.addRect(32, 6, 36, 41, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine66() {
+ SkPath path;
+ path.addRect(0, 0, 60, 60, SkPath::kCW_Direction);
+ path.addRect(0, 30, 20, 20, SkPath::kCW_Direction);
+ path.addRect(12, 20, 24, 30, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine66x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 60, 60, SkPath::kCW_Direction);
+ path.addRect(0, 30, 20, 20, SkPath::kCW_Direction);
+ path.addRect(12, 20, 24, 30, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine67() {
+ SkPath path;
+ path.addRect(0, 0, 60, 60, SkPath::kCW_Direction);
+ path.addRect(10, 40, 30, 30, SkPath::kCW_Direction);
+ path.addRect(24, 20, 36, 30, SkPath::kCW_Direction);
+ path.addRect(32, 0, 36, 41, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine67x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 60, 60, SkPath::kCW_Direction);
+ path.addRect(10, 40, 30, 30, SkPath::kCW_Direction);
+ path.addRect(24, 20, 36, 30, SkPath::kCW_Direction);
+ path.addRect(32, 0, 36, 41, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine68a() {
+ SkPath path;
+ path.addRect(0, 0, 8, 8, SkPath::kCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCW_Direction);
+ path.addRect(1, 2, 4, 2, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine68ax() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 8, 8, SkPath::kCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCW_Direction);
+ path.addRect(1, 2, 4, 2, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine68b() {
+ SkPath path;
+ path.addRect(0, 0, 8, 8, SkPath::kCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCCW_Direction);
+ path.addRect(1, 2, 2, 2, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine68bx() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 8, 8, SkPath::kCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCCW_Direction);
+ path.addRect(1, 2, 2, 2, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine68c() {
+ SkPath path;
+ path.addRect(0, 0, 8, 8, SkPath::kCCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCW_Direction);
+ path.addRect(1, 2, 4, 2, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine68cx() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 8, 8, SkPath::kCCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCW_Direction);
+ path.addRect(1, 2, 4, 2, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine68d() {
+ SkPath path;
+ path.addRect(0, 0, 8, 8, SkPath::kCCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCCW_Direction);
+ path.addRect(1, 2, 4, 2, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine68dx() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 8, 8, SkPath::kCCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCCW_Direction);
+ path.addRect(1, 2, 4, 2, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine68e() {
+ SkPath path;
+ path.addRect(0, 0, 8, 8, SkPath::kCW_Direction);
+ path.addRect(0, 0, 8, 8, SkPath::kCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCCW_Direction);
+ path.addRect(1, 2, 2, 2, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine68ex() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 8, 8, SkPath::kCW_Direction);
+ path.addRect(0, 0, 8, 8, SkPath::kCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCCW_Direction);
+ path.addRect(1, 2, 2, 2, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine68f() {
+ SkPath path;
+ path.addRect(0, 0, 8, 8, SkPath::kCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCCW_Direction);
+ path.addRect(1, 2, 2, 2, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine68fx() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 8, 8, SkPath::kCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCCW_Direction);
+ path.addRect(1, 2, 2, 2, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine68g() {
+ SkPath path;
+ path.addRect(0, 0, 8, 8, SkPath::kCW_Direction);
+ path.addRect(0, 0, 8, 8, SkPath::kCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCCW_Direction);
+ path.addRect(1, 2, 2, 2, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine68gx() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 8, 8, SkPath::kCW_Direction);
+ path.addRect(0, 0, 8, 8, SkPath::kCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCCW_Direction);
+ path.addRect(1, 2, 2, 2, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine68h() {
+ SkPath path;
+ path.addRect(0, 0, 8, 8, SkPath::kCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCCW_Direction);
+ path.addRect(1, 2, 2, 2, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine68hx() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 8, 8, SkPath::kCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCCW_Direction);
+ path.addRect(1, 2, 2, 2, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine69() {
+ SkPath path;
+ path.addRect(0, 20, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 20, 12, 30, SkPath::kCW_Direction);
+ path.addRect(12, 32, 21, 36, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine69x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 20, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 20, 12, 30, SkPath::kCW_Direction);
+ path.addRect(12, 32, 21, 36, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine70() {
+ SkPath path;
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 24, 12, 12, SkPath::kCW_Direction);
+ path.addRect(12, 32, 21, 36, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine70x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 24, 12, 12, SkPath::kCW_Direction);
+ path.addRect(12, 32, 21, 36, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine71() {
+ SkPath path;
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(12, 0, 24, 24, SkPath::kCW_Direction);
+ path.addRect(12, 32, 21, 36, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine71x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(12, 0, 24, 24, SkPath::kCW_Direction);
+ path.addRect(12, 32, 21, 36, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine72() {
+ SkPath path;
+ path.addRect(0, 0, 60, 60, SkPath::kCW_Direction);
+ path.addRect(10, 40, 30, 30, SkPath::kCW_Direction);
+ path.addRect(6, 20, 18, 30, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine72x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 60, 60, SkPath::kCW_Direction);
+ path.addRect(10, 40, 30, 30, SkPath::kCW_Direction);
+ path.addRect(6, 20, 18, 30, SkPath::kCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine73() {
+ SkPath path;
+ path.addRect(0, 0, 60, 60, SkPath::kCW_Direction);
+ path.addRect(0, 40, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 20, 12, 30, SkPath::kCW_Direction);
+ path.addRect(0, 0, 9, 9, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine73x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 60, 60, SkPath::kCW_Direction);
+ path.addRect(0, 40, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 20, 12, 30, SkPath::kCW_Direction);
+ path.addRect(0, 0, 9, 9, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine74() {
+ SkPath path;
+ path.addRect(20, 30, 40, 40, SkPath::kCW_Direction);
+ path.addRect(24, 20, 36, 30, SkPath::kCCW_Direction);
+ path.addRect(32, 24, 36, 41, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine74x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(20, 30, 40, 40, SkPath::kCW_Direction);
+ path.addRect(24, 20, 36, 30, SkPath::kCCW_Direction);
+ path.addRect(32, 24, 36, 41, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine75() {
+ SkPath path;
+ path.addRect(0, 0, 60, 60, SkPath::kCW_Direction);
+ path.addRect(10, 0, 30, 30, SkPath::kCCW_Direction);
+ path.addRect(18, 0, 30, 30, SkPath::kCCW_Direction);
+ path.addRect(12, 0, 21, 21, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine75x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 60, 60, SkPath::kCW_Direction);
+ path.addRect(10, 0, 30, 30, SkPath::kCCW_Direction);
+ path.addRect(18, 0, 30, 30, SkPath::kCCW_Direction);
+ path.addRect(12, 0, 21, 21, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine76() {
+ SkPath path;
+ path.addRect(36, 0, 66, 60, SkPath::kCW_Direction);
+ path.addRect(10, 20, 40, 30, SkPath::kCW_Direction);
+ path.addRect(24, 20, 36, 30, SkPath::kCCW_Direction);
+ path.addRect(32, 6, 36, 41, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine76x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(36, 0, 66, 60, SkPath::kCW_Direction);
+ path.addRect(10, 20, 40, 30, SkPath::kCW_Direction);
+ path.addRect(24, 20, 36, 30, SkPath::kCCW_Direction);
+ path.addRect(32, 6, 36, 41, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine77() {
+ SkPath path;
+ path.addRect(20, 0, 40, 40, SkPath::kCW_Direction);
+ path.addRect(24, 6, 36, 36, SkPath::kCCW_Direction);
+ path.addRect(24, 32, 33, 36, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine77x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(20, 0, 40, 40, SkPath::kCW_Direction);
+ path.addRect(24, 6, 36, 36, SkPath::kCCW_Direction);
+ path.addRect(24, 32, 33, 36, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine78() {
+ SkPath path;
+ path.addRect(0, 0, 30, 60, SkPath::kCW_Direction);
+ path.addRect(10, 20, 30, 30, SkPath::kCCW_Direction);
+ path.addRect(18, 20, 30, 30, SkPath::kCCW_Direction);
+ path.addRect(32, 0, 36, 41, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine78x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 30, 60, SkPath::kCW_Direction);
+ path.addRect(10, 20, 30, 30, SkPath::kCCW_Direction);
+ path.addRect(18, 20, 30, 30, SkPath::kCCW_Direction);
+ path.addRect(32, 0, 36, 41, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine79() {
+ SkPath path;
+ path.addRect(0, 36, 60, 30, SkPath::kCW_Direction);
+ path.addRect(10, 30, 40, 30, SkPath::kCW_Direction);
+ path.addRect(0, 20, 12, 30, SkPath::kCCW_Direction);
+ path.addRect(0, 32, 9, 36, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine79x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 36, 60, 30, SkPath::kCW_Direction);
+ path.addRect(10, 30, 40, 30, SkPath::kCW_Direction);
+ path.addRect(0, 20, 12, 30, SkPath::kCCW_Direction);
+ path.addRect(0, 32, 9, 36, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine81() {
+ SkPath path;
+ path.addRect(-1, -1, 3, 3, SkPath::kCW_Direction);
+ path.addRect(0, 0, 1, 1, SkPath::kCW_Direction);
+ path.addRect(0, 0, 1, 1, SkPath::kCW_Direction);
+ path.addRect(0, 0, 1, 1, SkPath::kCW_Direction);
+ path.addRect(1, 1, 2, 2, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testDegenerate1() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.lineTo(2, 0);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(2, 0);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testDegenerate1x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.lineTo(2, 0);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(2, 0);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testDegenerate2() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.lineTo(0, 0);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(0, 1);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testDegenerate2x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.lineTo(0, 0);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(0, 1);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testDegenerate3() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.lineTo(2, 0);
+ path.lineTo(1, 0);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.lineTo(3, 0);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testDegenerate3x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.lineTo(2, 0);
+ path.lineTo(1, 0);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.lineTo(3, 0);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testDegenerate4() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(1, 3);
+ path.close();
+ path.moveTo(1, 0);
+ path.lineTo(1, 1);
+ path.lineTo(1, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testDegenerate4x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(1, 3);
+ path.close();
+ path.moveTo(1, 0);
+ path.lineTo(1, 1);
+ path.lineTo(1, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testNondegenerate1() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.lineTo(3, 0);
+ path.lineTo(1, 3);
+ path.close();
+ path.moveTo(1, 1);
+ path.lineTo(2, 1);
+ path.lineTo(1, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testNondegenerate1x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.lineTo(3, 0);
+ path.lineTo(1, 3);
+ path.close();
+ path.moveTo(1, 1);
+ path.lineTo(2, 1);
+ path.lineTo(1, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testNondegenerate2() {
+ SkPath path;
+ path.moveTo(1, 0);
+ path.lineTo(0, 1);
+ path.lineTo(1, 1);
+ path.close();
+ path.moveTo(0, 2);
+ path.lineTo(0, 3);
+ path.lineTo(1, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testNondegenerate2x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(1, 0);
+ path.lineTo(0, 1);
+ path.lineTo(1, 1);
+ path.close();
+ path.moveTo(0, 2);
+ path.lineTo(0, 3);
+ path.lineTo(1, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testNondegenerate3() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(2, 1);
+ path.close();
+ path.moveTo(0, 1);
+ path.lineTo(1, 1);
+ path.lineTo(0, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testNondegenerate3x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(2, 1);
+ path.close();
+ path.moveTo(0, 1);
+ path.lineTo(1, 1);
+ path.lineTo(0, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testNondegenerate4() {
+ SkPath path;
+ path.moveTo(1, 0);
+ path.lineTo(0, 1);
+ path.lineTo(1, 2);
+ path.close();
+ path.moveTo(0, 2);
+ path.lineTo(0, 3);
+ path.lineTo(1, 3);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testNondegenerate4x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(1, 0);
+ path.lineTo(0, 1);
+ path.lineTo(1, 2);
+ path.close();
+ path.moveTo(0, 2);
+ path.lineTo(0, 3);
+ path.lineTo(1, 3);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadralateral5() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(1, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(2, 2);
+ path.lineTo(3, 2);
+ path.lineTo(3, 3);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadralateral5x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(1, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(2, 2);
+ path.lineTo(3, 2);
+ path.lineTo(3, 3);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadralateral6() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(1, 1);
+ path.close();
+ path.moveTo(1, 0);
+ path.lineTo(2, 0);
+ path.lineTo(0, 2);
+ path.lineTo(2, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadralateral6x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(1, 1);
+ path.close();
+ path.moveTo(1, 0);
+ path.lineTo(2, 0);
+ path.lineTo(0, 2);
+ path.lineTo(2, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testFauxQuadralateral6() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(1, 1);
+ path.close();
+ path.moveTo(1, 0);
+ path.lineTo(2, 0);
+ path.lineTo(1 + 1.0f/3, 2.0f/3);
+ path.close();
+ path.moveTo(1 + 1.0f/3, 2.0f/3);
+ path.lineTo(0, 2);
+ path.lineTo(2, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testFauxQuadralateral6x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(1, 1);
+ path.close();
+ path.moveTo(1, 0);
+ path.lineTo(2, 0);
+ path.lineTo(1 + 1.0f/3, 2.0f/3);
+ path.close();
+ path.moveTo(1 + 1.0f/3, 2.0f/3);
+ path.lineTo(0, 2);
+ path.lineTo(2, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testFauxQuadralateral6a() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.lineTo(3, 0);
+ path.lineTo(3, 3);
+ path.close();
+ path.moveTo(3, 0);
+ path.lineTo(6, 0);
+ path.lineTo(4, 2);
+ path.close();
+ path.moveTo(4, 2);
+ path.lineTo(0, 6);
+ path.lineTo(6, 6);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testFauxQuadralateral6ax() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.lineTo(3, 0);
+ path.lineTo(3, 3);
+ path.close();
+ path.moveTo(3, 0);
+ path.lineTo(6, 0);
+ path.lineTo(4, 2);
+ path.close();
+ path.moveTo(4, 2);
+ path.lineTo(0, 6);
+ path.lineTo(6, 6);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testFauxQuadralateral6b() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.lineTo(3, 0);
+ path.lineTo(3, 3);
+ path.close();
+ path.moveTo(3, 0);
+ path.lineTo(6, 0);
+ path.lineTo(4, 2);
+ path.close();
+ path.moveTo(4, 2);
+ path.lineTo(6, 6);
+ path.lineTo(0, 6);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testFauxQuadralateral6bx() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.lineTo(3, 0);
+ path.lineTo(3, 3);
+ path.close();
+ path.moveTo(3, 0);
+ path.lineTo(6, 0);
+ path.lineTo(4, 2);
+ path.close();
+ path.moveTo(4, 2);
+ path.lineTo(6, 6);
+ path.lineTo(0, 6);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testFauxQuadralateral6c() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.lineTo(3, 3);
+ path.lineTo(3, 0);
+ path.close();
+ path.moveTo(3, 0);
+ path.lineTo(6, 0);
+ path.lineTo(4, 2);
+ path.close();
+ path.moveTo(4, 2);
+ path.lineTo(0, 6);
+ path.lineTo(6, 6);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testFauxQuadralateral6cx() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.lineTo(3, 3);
+ path.lineTo(3, 0);
+ path.close();
+ path.moveTo(3, 0);
+ path.lineTo(6, 0);
+ path.lineTo(4, 2);
+ path.close();
+ path.moveTo(4, 2);
+ path.lineTo(0, 6);
+ path.lineTo(6, 6);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testFauxQuadralateral6d() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.lineTo(3, 3);
+ path.lineTo(3, 0);
+ path.close();
+ path.moveTo(3, 0);
+ path.lineTo(6, 0);
+ path.lineTo(4, 2);
+ path.close();
+ path.moveTo(4, 2);
+ path.lineTo(6, 6);
+ path.lineTo(0, 6);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testFauxQuadralateral6dx() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.lineTo(3, 3);
+ path.lineTo(3, 0);
+ path.close();
+ path.moveTo(3, 0);
+ path.lineTo(6, 0);
+ path.lineTo(4, 2);
+ path.close();
+ path.moveTo(4, 2);
+ path.lineTo(6, 6);
+ path.lineTo(0, 6);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadralateral6a() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.lineTo(3, 0);
+ path.lineTo(3, 3);
+ path.close();
+ path.moveTo(3, 0);
+ path.lineTo(6, 0);
+ path.lineTo(0, 6);
+ path.lineTo(6, 6);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadralateral6ax() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.lineTo(3, 0);
+ path.lineTo(3, 3);
+ path.close();
+ path.moveTo(3, 0);
+ path.lineTo(6, 0);
+ path.lineTo(0, 6);
+ path.lineTo(6, 6);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadralateral7() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(2, 1);
+ path.close();
+ path.moveTo(1, 0);
+ path.lineTo(1, 1);
+ path.lineTo(2, 2);
+ path.lineTo(1, 3);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadralateral7x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(2, 1);
+ path.close();
+ path.moveTo(1, 0);
+ path.lineTo(1, 1);
+ path.lineTo(2, 2);
+ path.lineTo(1, 3);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadralateral8() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.lineTo(3, 1);
+ path.lineTo(1, 3);
+ path.lineTo(3, 3);
+ path.close();
+ path.moveTo(2, 1);
+ path.lineTo(0, 2);
+ path.lineTo(3, 2);
+ path.lineTo(2, 3);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadralateral8x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.lineTo(3, 1);
+ path.lineTo(1, 3);
+ path.lineTo(3, 3);
+ path.close();
+ path.moveTo(2, 1);
+ path.lineTo(0, 2);
+ path.lineTo(3, 2);
+ path.lineTo(2, 3);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadralateral9() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(1, 2);
+ path.lineTo(2, 2);
+ path.close();
+ path.moveTo(1, 1);
+ path.lineTo(2, 1);
+ path.lineTo(1, 3);
+ path.lineTo(2, 3);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadralateral9x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(1, 2);
+ path.lineTo(2, 2);
+ path.close();
+ path.moveTo(1, 1);
+ path.lineTo(2, 1);
+ path.lineTo(1, 3);
+ path.lineTo(2, 3);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testLine1a() {
+ SkPath path;
+ path.setFillType(SkPath::kWinding_FillType);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 0, 13, 13, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine1ax() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 0, 13, 13, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine2ax() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 20, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 20, 12, 30, SkPath::kCW_Direction);
+ path.addRect(12, 0, 21, 21, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine3aax() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(10, 30, 30, 30, SkPath::kCW_Direction);
+ path.addRect(18, 20, 30, 30, SkPath::kCCW_Direction);
+ path.addRect(0, 32, 9, 36, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testLine4ax() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(10, 30, 30, 30, SkPath::kCW_Direction);
+ path.addRect(24, 20, 36, 30, SkPath::kCCW_Direction);
+ path.addRect(0, 32, 9, 36, SkPath::kCCW_Direction);
+ testSimplifyx(path);
+}
+
+static void testQuadratic1() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.lineTo(1, 0);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic1x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.lineTo(1, 0);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic2() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.lineTo(3, 0);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic2x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.lineTo(3, 0);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic3() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 1, 0);
+ path.lineTo(0, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic3x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 1, 0);
+ path.lineTo(0, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic4() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 1, 0);
+ path.lineTo(0, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(1, 0, 0, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic4x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 1, 0);
+ path.lineTo(0, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(1, 0, 0, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic5() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.lineTo(0, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.quadTo(0, 1, 0, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic6() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 1, 0);
+ path.lineTo(2, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(2, 0, 0, 1);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic7() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 1, 0);
+ path.lineTo(3, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(3, 0, 1, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic8() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 1, 0);
+ path.lineTo(0, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.quadTo(0, 1, 1, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic9() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 1, 0);
+ path.lineTo(3, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.quadTo(1, 2, 3, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic14() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 1, 0);
+ path.lineTo(3, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.quadTo(3, 2, 3, 3);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic15() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 1, 0);
+ path.lineTo(1, 3);
+ path.close();
+ path.moveTo(1, 0);
+ path.lineTo(0, 1);
+ path.quadTo(1, 1, 0, 3);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic17x() {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 3, 1);
+ path.lineTo(0, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.quadTo(3, 1, 0, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic18() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.lineTo(0, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(1, 0, 1, 1);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic19() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.lineTo(0, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(2, 0, 0, 1);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic20() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.lineTo(0, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic21() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.lineTo(0, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(1, 0, 0, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic22() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.lineTo(0, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(0, 1, 2, 1);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic23() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.lineTo(0, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(0, 2, 1, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic24() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.lineTo(0, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.quadTo(2, 0, 0, 1);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic25() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 1, 1);
+ path.lineTo(1, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(2, 1, 0, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic26() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 1, 1);
+ path.lineTo(0, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic27() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 1, 1);
+ path.lineTo(2, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(2, 1, 0, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic28() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.lineTo(0, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 2);
+ path.quadTo(1, 2, 0, 3);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic29() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 2, 1);
+ path.lineTo(0, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic30() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 1, 2);
+ path.lineTo(1, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.quadTo(0, 1, 1, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic31() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 1, 2);
+ path.lineTo(1, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.quadTo(0, 1, 1, 3);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic32() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 2, 3);
+ path.lineTo(2, 3);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(3, 1, 0, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic33() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(2, 0, 0, 1);
+ path.lineTo(0, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 1);
+ path.quadTo(2, 1, 2, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic34() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(2, 0, 0, 1);
+ path.lineTo(0, 1);
+ path.close();
+ path.moveTo(1, 0);
+ path.lineTo(1, 1);
+ path.quadTo(2, 1, 1, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic35() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(0, 1, 1, 1);
+ path.lineTo(1, 3);
+ path.close();
+ path.moveTo(2, 0);
+ path.lineTo(3, 0);
+ path.quadTo(0, 1, 1, 1);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic36() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(2, 1, 2, 3);
+ path.lineTo(2, 3);
+ path.close();
+ path.moveTo(3, 1);
+ path.lineTo(1, 2);
+ path.quadTo(3, 2, 1, 3);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic37() {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.quadTo(0, 2, 1, 2);
+ path.lineTo(1, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(3, 1);
+ path.quadTo(0, 2, 1, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic38() {
+ SkPath path;
+ path.moveTo(1, 0);
+ path.quadTo(0, 1, 1, 1);
+ path.lineTo(1, 1);
+ path.close();
+ path.moveTo(1, 0);
+ path.lineTo(1, 2);
+ path.quadTo(2, 2, 1, 3);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic51() {
+ SkPath path;
+ path.moveTo(369.863983f, 145.645813f);
+ path.quadTo(382.380371f, 121.254936f, 406.236359f, 121.254936f);
+ path.lineTo(369.863983f, 145.645813f);
+ path.close();
+ path.moveTo(369.970581f, 137.94342f);
+ path.quadTo(383.98465f, 121.254936f, 406.235992f, 121.254936f);
+ path.lineTo(369.970581f, 137.94342f);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic53() {
+ SkPath path;
+ path.moveTo(303.12088f, 141.299606f);
+ path.lineTo(330.463562f, 217.659027f);
+ path.lineTo(303.12088f, 141.299606f);
+ path.close();
+ path.moveTo(371.919067f, 205.854996f);
+ path.lineTo(326.236786f, 205.854996f);
+ path.quadTo(329.104431f, 231.663818f, 351.512085f, 231.663818f);
+ path.lineTo(371.919067f, 205.854996f);
+ path.close();
+ testSimplifyx(path);
+}
+static void testQuadratic55() {
+ SkPath path;
+path.moveTo(303.12088f, 141.299606f);
+path.lineTo(330.463562f, 217.659027f);
+path.lineTo(358.606506f, 141.299606f);
+path.lineTo(303.12088f, 141.299606f);
+path.close();
+path.moveTo(326.236786f, 205.854996f);
+path.quadTo(329.104431f, 231.663818f, 351.512085f, 231.663818f);
+path.lineTo(326.236786f, 205.854996f);
+path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic56() {
+ SkPath path;
+path.moveTo(366.608826f, 151.196014f);
+path.quadTo(378.803101f, 136.674606f, 398.164948f, 136.674606f);
+path.lineTo(354.009216f, 208.816208f);
+path.lineTo(393.291473f, 102.232819f);
+path.lineTo(359.978058f, 136.581512f);
+path.quadTo(378.315979f, 136.581512f, 388.322723f, 149.613556f);
+path.lineTo(364.390686f, 157.898193f);
+path.quadTo(375.281769f, 136.674606f, 396.039917f, 136.674606f);
+path.lineTo(350, 120);
+path.lineTo(366.608826f, 151.196014f);
+path.close();
+ testSimplifyx(path);
+}
+
+static void testLine80() {
+ SkPath path;
+path.moveTo(4, 0);
+path.lineTo(3, 7);
+path.lineTo(7, 5);
+path.lineTo(2, 2);
+path.close();
+path.moveTo(0, 6);
+path.lineTo(6, 12);
+path.lineTo(8, 3);
+path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic58() {
+ SkPath path;
+path.moveTo(283.714233f, 240);
+path.lineTo(283.714233f, 141.299606f);
+path.lineTo(303.12088f, 141.299606f);
+path.lineTo(330.463562f, 217.659027f);
+path.lineTo(358.606506f, 141.299606f);
+path.lineTo(362.874634f, 159.705902f);
+path.lineTo(335.665344f, 233.397751f);
+path.lineTo(322.12738f, 233.397751f);
+path.lineTo(295.718353f, 159.505829f);
+path.lineTo(295.718353f, 240);
+path.lineTo(283.714233f, 240);
+path.close();
+path.moveTo(322.935669f, 231.030273f);
+path.quadTo(312.832214f, 220.393295f, 312.832214f, 203.454178f);
+path.quadTo(312.832214f, 186.981888f, 321.73526f, 176.444946f);
+path.quadTo(330.638306f, 165.90802f, 344.509705f, 165.90802f);
+path.quadTo(357.647522f, 165.90802f, 364.81665f, 175.244537f);
+path.lineTo(371.919067f, 205.854996f);
+path.lineTo(326.236786f, 205.854996f);
+path.quadTo(329.104431f, 231.663818f, 351.512085f, 231.663818f);
+path.lineTo(322.935669f, 231.030273f);
+path.close();
+path.moveTo(326.837006f, 195.984955f);
+path.lineTo(358.78125f, 195.984955f);
+path.quadTo(358.78125f, 175.778046f, 343.709442f, 175.778046f);
+path.quadTo(328.570923f, 175.778046f, 326.837006f, 195.984955f);
+path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic59x() {
+ SkPath path, pathB;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.lineTo(2, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(2, 0);
+ path.quadTo(3, 1, 1, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic59() {
+ SkPath path, pathB;
+ path.setFillType(SkPath::kWinding_FillType);
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.lineTo(2, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(2, 0);
+ path.quadTo(3, 1, 1, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic63() {
+ SkPath path, pathB;
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.lineTo(3, 2);
+ path.close();
+ path.moveTo(1, 0);
+ path.lineTo(2, 1);
+ path.quadTo(2, 1, 2, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic64() {
+ SkPath path, pathB;
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.lineTo(2, 3);
+ path.close();
+ path.moveTo(1, 2);
+ path.lineTo(2, 2);
+ path.quadTo(0, 3, 3, 3);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic65() {
+ SkPath path, pathB;
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.lineTo(3, 2);
+ path.close();
+ path.moveTo(2, 1);
+ path.lineTo(2, 2);
+ path.quadTo(0, 3, 1, 3);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic67x() {
+ SkPath path, pathB;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 2, 1);
+ path.lineTo(2, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(2, 0);
+ path.quadTo(1, 1, 3, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic68() {
+ SkPath path, pathB;
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.lineTo(1, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(0, 1, 2, 1);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic69() {
+ SkPath path, pathB;
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 1);
+ path.lineTo(3, 2);
+ path.close();
+ path.moveTo(2, 0);
+ path.lineTo(1, 1);
+ path.quadTo(3, 2, 2, 3);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic70x() {
+ SkPath path, pathB;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.lineTo(1, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(0, 1, 2, 1);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic71() {
+ SkPath path, pathB;
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 1, 1);
+ path.lineTo(3, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(1, 1, 3, 1);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic72() {
+ SkPath path, pathB;
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 1, 2);
+ path.lineTo(1, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.quadTo(0, 1, 3, 2);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic73() {
+ SkPath path, pathB;
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 0, 3);
+ path.lineTo(0, 3);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.quadTo(0, 1, 1, 1);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic74() {
+ SkPath path, pathB;
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 1, 3);
+ path.lineTo(1, 3);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 1);
+ path.quadTo(3, 2, 2, 3);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void testQuadratic75() {
+ SkPath path, pathB;
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 1, 3);
+ path.lineTo(2, 3);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 1);
+ path.quadTo(3, 2, 2, 3);
+ path.close();
+ testSimplifyx(path);
+}
+
+static void (*firstTest)() = testQuadratic63;
+
+static struct {
+ void (*fun)();
+ const char* str;
+} tests[] = {
+// TEST(testQuadratic75),
+ TEST(testQuadratic74),
+ TEST(testQuadratic73),
+ TEST(testQuadratic72),
+ TEST(testQuadratic71),
+ TEST(testQuadratic70x),
+ TEST(testQuadratic69),
+ TEST(testQuadratic68),
+ TEST(testQuadratic67x),
+ TEST(testQuadratic65),
+ TEST(testQuadratic64),
+ TEST(testQuadratic63),
+ TEST(testLine1a),
+ TEST(testLine1ax),
+ TEST(testQuadratic59),
+ TEST(testQuadratic59x),
+ TEST(testQuadratic58),
+ TEST(testQuadratic56),
+ TEST(testQuadratic55),
+ TEST(testQuadratic53),
+ TEST(testQuadratic51),
+ TEST(testQuadratic38),
+ TEST(testQuadratic37),
+ TEST(testQuadratic36),
+ TEST(testQuadratic35),
+ TEST(testQuadratic34),
+ TEST(testQuadratic33),
+ TEST(testQuadratic32),
+ TEST(testQuadratic31),
+ TEST(testQuadratic30),
+ TEST(testQuadratic29),
+ TEST(testQuadratic28),
+ TEST(testQuadratic27),
+ TEST(testQuadratic26),
+ TEST(testQuadratic25),
+ TEST(testQuadratic24),
+ TEST(testQuadratic23),
+ TEST(testQuadratic22),
+ TEST(testQuadratic21),
+ TEST(testQuadratic20),
+ TEST(testQuadratic19),
+ TEST(testQuadratic18),
+ TEST(testQuadratic17x),
+ TEST(testQuadratic15),
+ TEST(testQuadratic14),
+ TEST(testQuadratic9),
+ TEST(testQuadratic8),
+ TEST(testQuadratic7),
+ TEST(testQuadratic6),
+ TEST(testQuadratic5),
+ TEST(testQuadratic4x),
+ TEST(testQuadratic3x),
+ TEST(testQuadratic2x),
+ TEST(testQuadratic1x),
+ TEST(testQuadratic4),
+ TEST(testQuadratic3),
+ TEST(testQuadratic2),
+ TEST(testQuadratic1),
+ TEST(testLine4ax),
+ TEST(testLine3aax),
+ TEST(testLine2ax),
+ TEST(testLine1ax),
+ TEST(testQuadralateral9x),
+ TEST(testQuadralateral8x),
+ TEST(testQuadralateral7x),
+ TEST(testQuadralateral6x),
+ TEST(testQuadralateral6ax),
+ TEST(testQuadralateral9),
+ TEST(testQuadralateral8),
+ TEST(testQuadralateral7),
+ TEST(testQuadralateral6),
+ TEST(testQuadralateral6a),
+ TEST(testFauxQuadralateral6dx),
+ TEST(testFauxQuadralateral6cx),
+ TEST(testFauxQuadralateral6bx),
+ TEST(testFauxQuadralateral6ax),
+ TEST(testFauxQuadralateral6x),
+ TEST(testFauxQuadralateral6d),
+ TEST(testFauxQuadralateral6c),
+ TEST(testFauxQuadralateral6b),
+ TEST(testFauxQuadralateral6a),
+ TEST(testFauxQuadralateral6),
+ TEST(testQuadralateral5x),
+ TEST(testQuadralateral5),
+ TEST(testNondegenerate4x),
+ TEST(testNondegenerate3x),
+ TEST(testNondegenerate2x),
+ TEST(testNondegenerate1x),
+ TEST(testNondegenerate4),
+ TEST(testNondegenerate3),
+ TEST(testNondegenerate2),
+ TEST(testNondegenerate1),
+ TEST(testDegenerate4x),
+ TEST(testDegenerate3x),
+ TEST(testDegenerate2x),
+ TEST(testDegenerate1x),
+ TEST(testDegenerate4),
+ TEST(testDegenerate3),
+ TEST(testDegenerate2),
+ TEST(testDegenerate1),
+ TEST(testLine79x),
+ TEST(testLine78x),
+ TEST(testLine77x),
+ TEST(testLine76x),
+ TEST(testLine75x),
+ TEST(testLine74x),
+ TEST(testLine73x),
+ TEST(testLine72x),
+ TEST(testLine71x),
+ TEST(testLine70x),
+ TEST(testLine69x),
+ TEST(testLine68hx),
+ TEST(testLine68gx),
+ TEST(testLine68fx),
+ TEST(testLine68ex),
+ TEST(testLine68dx),
+ TEST(testLine68cx),
+ TEST(testLine68bx),
+ TEST(testLine68ax),
+ TEST(testLine67x),
+ TEST(testLine66x),
+ TEST(testLine65x),
+ TEST(testLine64x),
+ TEST(testLine63x),
+ TEST(testLine62x),
+ TEST(testLine61x),
+ TEST(testLine60x),
+ TEST(testLine59x),
+ TEST(testLine58x),
+ TEST(testLine57x),
+ TEST(testLine56x),
+ TEST(testLine55x),
+ TEST(testLine54x),
+ TEST(testLine53x),
+ TEST(testLine52x),
+ TEST(testLine51x),
+ TEST(testLine50x),
+ TEST(testLine49x),
+ TEST(testLine48x),
+ TEST(testLine47x),
+ TEST(testLine46x),
+ TEST(testLine45x),
+ TEST(testLine44x),
+ TEST(testLine43x),
+ TEST(testLine42x),
+ TEST(testLine41x),
+ TEST(testLine40x),
+ TEST(testLine38x),
+ TEST(testLine37x),
+ TEST(testLine36x),
+ TEST(testLine35x),
+ TEST(testLine34x),
+ TEST(testLine33x),
+ TEST(testLine32x),
+ TEST(testLine31x),
+ TEST(testLine30x),
+ TEST(testLine29x),
+ TEST(testLine28x),
+ TEST(testLine27x),
+ TEST(testLine26x),
+ TEST(testLine25x),
+ TEST(testLine24ax),
+ TEST(testLine24x),
+ TEST(testLine23x),
+ TEST(testLine22x),
+ TEST(testLine21x),
+ TEST(testLine20x),
+ TEST(testLine19x),
+ TEST(testLine18x),
+ TEST(testLine17x),
+ TEST(testLine16x),
+ TEST(testLine15x),
+ TEST(testLine14x),
+ TEST(testLine13x),
+ TEST(testLine12x),
+ TEST(testLine11x),
+ TEST(testLine10ax),
+ TEST(testLine10x),
+ TEST(testLine9x),
+ TEST(testLine8x),
+ TEST(testLine7bx),
+ TEST(testLine7ax),
+ TEST(testLine7x),
+ TEST(testLine6x),
+ TEST(testLine5x),
+ TEST(testLine4x),
+ TEST(testLine3bx),
+ TEST(testLine3ax),
+ TEST(testLine3x),
+ TEST(testLine2x),
+ TEST(testLine1x),
+ TEST(testLine81),
+ TEST(testLine80),
+ TEST(testLine79),
+ TEST(testLine78),
+ TEST(testLine77),
+ TEST(testLine76),
+ TEST(testLine75),
+ TEST(testLine74),
+ TEST(testLine73),
+ TEST(testLine72),
+ TEST(testLine71),
+ TEST(testLine70),
+ TEST(testLine69),
+ TEST(testLine68h),
+ TEST(testLine68g),
+ TEST(testLine68f),
+ TEST(testLine68e),
+ TEST(testLine68d),
+ TEST(testLine68c),
+ TEST(testLine68b),
+ TEST(testLine68a),
+ TEST(testLine67),
+ TEST(testLine66),
+ TEST(testLine65),
+ TEST(testLine64),
+ TEST(testLine63),
+ TEST(testLine62),
+ TEST(testLine61),
+ TEST(testLine60),
+ TEST(testLine59),
+ TEST(testLine58),
+ TEST(testLine57),
+ TEST(testLine56),
+ TEST(testLine55),
+ TEST(testLine54),
+ TEST(testLine53),
+ TEST(testLine52),
+ TEST(testLine51),
+ TEST(testLine50),
+ TEST(testLine49),
+ TEST(testLine48),
+ TEST(testLine47),
+ TEST(testLine46),
+ TEST(testLine45),
+ TEST(testLine44),
+ TEST(testLine43),
+ TEST(testLine42),
+ TEST(testLine41),
+ TEST(testLine40),
+ TEST(testLine38),
+ TEST(testLine37),
+ TEST(testLine36),
+ TEST(testLine35),
+ TEST(testLine34),
+ TEST(testLine33),
+ TEST(testLine32),
+ TEST(testLine31),
+ TEST(testLine30),
+ TEST(testLine29),
+ TEST(testLine28),
+ TEST(testLine27),
+ TEST(testLine26),
+ TEST(testLine25),
+ TEST(testLine24a),
+ TEST(testLine24),
+ TEST(testLine23),
+ TEST(testLine22),
+ TEST(testLine21),
+ TEST(testLine20),
+ TEST(testLine19),
+ TEST(testLine18),
+ TEST(testLine17),
+ TEST(testLine16),
+ TEST(testLine15),
+ TEST(testLine14),
+ TEST(testLine13),
+ TEST(testLine12),
+ TEST(testLine11),
+ TEST(testLine10a),
+ TEST(testLine10),
+ TEST(testLine9),
+ TEST(testLine8),
+ TEST(testLine7b),
+ TEST(testLine7a),
+ TEST(testLine7),
+ TEST(testLine6),
+ TEST(testLine5),
+ TEST(testLine4),
+ TEST(testLine3b),
+ TEST(testLine3a),
+ TEST(testLine3),
+ TEST(testLine2),
+ TEST(testLine1),
+};
+
+static void testIntersect1() {
+ SkPath one, two;
+ one.addRect(0, 0, 6, 6, SkPath::kCW_Direction);
+ two.addRect(3, 3, 9, 9, SkPath::kCW_Direction);
+ testShapeOp(one, two, kIntersect_Op);
+}
+
+static void testUnion1() {
+ SkPath one, two;
+ one.addRect(0, 0, 6, 6, SkPath::kCW_Direction);
+ two.addRect(3, 3, 9, 9, SkPath::kCW_Direction);
+ testShapeOp(one, two, kUnion_Op);
+}
+
+static void testDiff1() {
+ SkPath one, two;
+ one.addRect(0, 0, 6, 6, SkPath::kCW_Direction);
+ two.addRect(3, 3, 9, 9, SkPath::kCW_Direction);
+ testShapeOp(one, two, kDifference_Op);
+}
+
+static void testXor1() {
+ SkPath one, two;
+ one.addRect(0, 0, 6, 6, SkPath::kCW_Direction);
+ two.addRect(3, 3, 9, 9, SkPath::kCW_Direction);
+ testShapeOp(one, two, kXor_Op);
+}
+
+static void testIntersect2() {
+ SkPath one, two;
+ one.addRect(0, 0, 6, 6, SkPath::kCW_Direction);
+ two.addRect(0, 3, 9, 9, SkPath::kCW_Direction);
+ testShapeOp(one, two, kIntersect_Op);
+}
+
+static void testUnion2() {
+ SkPath one, two;
+ one.addRect(0, 0, 6, 6, SkPath::kCW_Direction);
+ two.addRect(0, 3, 9, 9, SkPath::kCW_Direction);
+ testShapeOp(one, two, kUnion_Op);
+}
+
+static void testDiff2() {
+ SkPath one, two;
+ one.addRect(0, 0, 6, 6, SkPath::kCW_Direction);
+ two.addRect(0, 3, 9, 9, SkPath::kCW_Direction);
+ testShapeOp(one, two, kDifference_Op);
+}
+
+static void testXor2() {
+ SkPath one, two;
+ one.addRect(0, 0, 6, 6, SkPath::kCW_Direction);
+ two.addRect(0, 3, 9, 9, SkPath::kCW_Direction);
+ testShapeOp(one, two, kXor_Op);
+}
+
+static void testOp1d() {
+ SkPath path, pathB;
+ path.setFillType(SkPath::kWinding_FillType);
+ path.addRect(0, 0, 1, 1, SkPath::kCW_Direction);
+ path.addRect(0, 0, 2, 2, SkPath::kCW_Direction);
+ pathB.setFillType(SkPath::kWinding_FillType);
+ pathB.addRect(0, 0, 1, 1, SkPath::kCW_Direction);
+ pathB.addRect(0, 0, 1, 1, SkPath::kCW_Direction);
+ testShapeOp(path, pathB, kDifference_Op);
+}
+
+static void testOp2d() {
+ SkPath path, pathB;
+ path.setFillType(SkPath::kWinding_FillType);
+ path.addRect(0, 0, 1, 1, SkPath::kCW_Direction);
+ path.addRect(0, 0, 2, 2, SkPath::kCW_Direction);
+ pathB.setFillType(SkPath::kEvenOdd_FillType);
+ pathB.addRect(0, 0, 1, 1, SkPath::kCW_Direction);
+ pathB.addRect(0, 0, 1, 1, SkPath::kCW_Direction);
+ testShapeOp(path, pathB, kDifference_Op);
+}
+
+static void testOp3d() {
+ SkPath path, pathB;
+ path.setFillType(SkPath::kWinding_FillType);
+ path.addRect(0, 0, 1, 1, SkPath::kCW_Direction);
+ path.addRect(1, 1, 2, 2, SkPath::kCW_Direction);
+ pathB.setFillType(SkPath::kWinding_FillType);
+ pathB.addRect(0, 0, 1, 1, SkPath::kCW_Direction);
+ pathB.addRect(0, 0, 1, 1, SkPath::kCW_Direction);
+ testShapeOp(path, pathB, kDifference_Op);
+}
+
+static void testOp1u() {
+ SkPath path, pathB;
+ path.setFillType(SkPath::kWinding_FillType);
+ path.addRect(0, 0, 1, 1, SkPath::kCW_Direction);
+ path.addRect(0, 0, 3, 3, SkPath::kCW_Direction);
+ pathB.setFillType(SkPath::kWinding_FillType);
+ pathB.addRect(0, 0, 1, 1, SkPath::kCW_Direction);
+ pathB.addRect(0, 0, 1, 1, SkPath::kCW_Direction);
+ testShapeOp(path, pathB, kUnion_Op);
+}
+
+static void testOp4d() {
+ SkPath path, pathB;
+ path.setFillType(SkPath::kWinding_FillType);
+ path.addRect(0, 0, 1, 1, SkPath::kCW_Direction);
+ path.addRect(2, 2, 4, 4, SkPath::kCW_Direction);
+ pathB.setFillType(SkPath::kWinding_FillType);
+ pathB.addRect(0, 0, 1, 1, SkPath::kCW_Direction);
+ pathB.addRect(0, 0, 1, 1, SkPath::kCW_Direction);
+ testShapeOp(path, pathB, kDifference_Op);
+}
+
+static void testOp5d() {
+ SkPath path, pathB;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 2, 2, SkPath::kCW_Direction);
+ path.addRect(0, 0, 3, 3, SkPath::kCW_Direction);
+ pathB.setFillType(SkPath::kEvenOdd_FillType);
+ pathB.addRect(0, 0, 1, 1, SkPath::kCW_Direction);
+ pathB.addRect(0, 0, 1, 1, SkPath::kCW_Direction);
+ testShapeOp(path, pathB, kDifference_Op);
+}
+
+static void testOp6d() {
+ SkPath path, pathB;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 1, 1, SkPath::kCW_Direction);
+ path.addRect(0, 0, 3, 3, SkPath::kCW_Direction);
+ pathB.setFillType(SkPath::kWinding_FillType);
+ pathB.addRect(0, 0, 1, 1, SkPath::kCW_Direction);
+ pathB.addRect(0, 0, 1, 1, SkPath::kCW_Direction);
+ testShapeOp(path, pathB, kDifference_Op);
+}
+
+static void testOp7d() {
+ SkPath path, pathB;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 2, 2, SkPath::kCW_Direction);
+ path.addRect(0, 0, 1, 1, SkPath::kCW_Direction);
+ pathB.setFillType(SkPath::kEvenOdd_FillType);
+ pathB.addRect(0, 0, 1, 1, SkPath::kCW_Direction);
+ pathB.addRect(0, 0, 1, 1, SkPath::kCW_Direction);
+ testShapeOp(path, pathB, kDifference_Op);
+}
+
+static void testOp2u() {
+ SkPath path, pathB;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 2, 2, SkPath::kCW_Direction);
+ path.addRect(0, 0, 2, 2, SkPath::kCW_Direction);
+ pathB.setFillType(SkPath::kWinding_FillType);
+ pathB.addRect(0, 0, 3, 3, SkPath::kCW_Direction);
+ pathB.addRect(1, 1, 2, 2, SkPath::kCW_Direction);
+ testShapeOp(path, pathB, kUnion_Op);
+}
+
+static const size_t testCount = sizeof(tests) / sizeof(tests[0]);
+
+static struct {
+ void (*fun)();
+ const char* str;
+} subTests[] = {
+ TEST(testDiff1),
+ TEST(testIntersect1),
+ TEST(testUnion1),
+ TEST(testXor1),
+ TEST(testDiff2),
+ TEST(testIntersect2),
+ TEST(testUnion2),
+ TEST(testXor2),
+ TEST(testOp1d),
+ TEST(testOp2d),
+ TEST(testOp3d),
+ TEST(testOp1u),
+ TEST(testOp4d),
+ TEST(testOp5d),
+ TEST(testOp6d),
+ TEST(testOp7d),
+ TEST(testOp2u),
+};
+
+static const size_t subTestCount = sizeof(subTests) / sizeof(subTests[0]);
+
+static void (*firstBinaryTest)() = 0;
+
+static bool skipAll = false;
+static bool runBinaryTestsFirst = false;
+static bool runReverse = false;
+
+void SimplifyNew_Test() {
+ if (skipAll) {
+ return;
+ }
+#ifdef SK_DEBUG
+ gDebugMaxWindSum = 4;
+ gDebugMaxWindValue = 4;
+ size_t index;
+#endif
+ if (runBinaryTestsFirst && firstBinaryTest) {
+ index = subTestCount - 1;
+ while (index > 0 && subTests[index].fun != firstBinaryTest) {
+ --index;
+ }
+ SkDebugf(" %s [%s]\n", __FUNCTION__, subTests[index].str);
+ (*subTests[index].fun)();
+ }
+ if (runBinaryTestsFirst) {
+ index = subTestCount - 1;
+ do {
+ SkDebugf(" %s [%s]\n", __FUNCTION__, subTests[index].str);
+ (*subTests[index].fun)();
+ } while (index--);
+ }
+ index = testCount - 1;
+ if (firstTest) {
+ while (index > 0 && tests[index].fun != firstTest) {
+ --index;
+ }
+ SkDebugf(" %s [%s]\n", __FUNCTION__, tests[index].str);
+ (*tests[index].fun)();
+ }
+ index = runReverse ? testCount - 1 : 0;
+ size_t last = runReverse ? 0 : testCount - 1;
+ bool firstTestComplete = false;
+ do {
+ SkDebugf(" %s [%s]\n", __FUNCTION__, tests[index].str);
+ (*tests[index].fun)();
+ firstTestComplete = true;
+ if (index == last) {
+ break;
+ }
+ index += runReverse ? -1 : 1;
+ } while (true);
+ if (!runBinaryTestsFirst) {
+ index = subTestCount - 1;
+ do {
+ SkDebugf(" %s [%s]\n", __FUNCTION__, subTests[index].str);
+ (*subTests[index].fun)();
+ } while (index--);
+ }
+#ifdef SK_DEBUG
+ gDebugMaxWindSum = SK_MaxS32;
+ gDebugMaxWindValue = SK_MaxS32;
+#endif
+}
diff --git a/experimental/Intersection/SimplifyRect4x4_Test.cpp b/experimental/Intersection/SimplifyRect4x4_Test.cpp
new file mode 100644
index 0000000000..5d857e9a74
--- /dev/null
+++ b/experimental/Intersection/SimplifyRect4x4_Test.cpp
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "EdgeWalker_Test.h"
+#include "Intersection_Tests.h"
+#include "ShapeOps.h"
+
+// four rects, of four sizes
+// for 3 smaller sizes, tall, wide
+ // top upper mid lower bottom aligned (3 bits, 5 values)
+ // same with x (3 bits, 5 values)
+// not included, square, tall, wide (2 bits)
+// cw or ccw (1 bit)
+
+static void* testSimplify4x4RectsMain(void* data)
+{
+ SkASSERT(data);
+ State4& state = *(State4*) data;
+ char pathStr[1024]; // gdb: set print elements 400
+ bzero(pathStr, sizeof(pathStr));
+ do {
+ int aShape = state.a & 0x03;
+ SkPath::Direction aCW = state.a >> 2 ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
+ int bShape = state.b & 0x03;
+ SkPath::Direction bCW = state.b >> 2 ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
+ int cShape = state.c & 0x03;
+ SkPath::Direction cCW = state.c >> 2 ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
+ int dShape = state.d & 0x03;
+ SkPath::Direction dCW = state.d >> 2 ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
+ for (int aXAlign = 0 ; aXAlign < 5; ++aXAlign) {
+ for (int aYAlign = 0 ; aYAlign < 5; ++aYAlign) {
+ for (int bXAlign = 0 ; bXAlign < 5; ++bXAlign) {
+ for (int bYAlign = 0 ; bYAlign < 5; ++bYAlign) {
+ for (int cXAlign = 0 ; cXAlign < 5; ++cXAlign) {
+ for (int cYAlign = 0 ; cYAlign < 5; ++cYAlign) {
+ for (int dXAlign = 0 ; dXAlign < 5; ++dXAlign) {
+ for (int dYAlign = 0 ; dYAlign < 5; ++dYAlign) {
+ SkPath path, out;
+ char* str = pathStr;
+ path.setFillType(SkPath::kWinding_FillType);
+ int l, t, r, b;
+ if (aShape) {
+ switch (aShape) {
+ case 1: // square
+ l = 0; r = 60;
+ t = 0; b = 60;
+ aXAlign = 5;
+ aYAlign = 5;
+ break;
+ case 2:
+ l = aXAlign * 12;
+ r = l + 30;
+ t = 0; b = 60;
+ aYAlign = 5;
+ break;
+ case 3:
+ l = 0; r = 60;
+ t = aYAlign * 12;
+ b = l + 30;
+ aXAlign = 5;
+ break;
+ }
+ path.addRect(l, t, r, b, aCW);
+ str += sprintf(str, " path.addRect(%d, %d, %d, %d,"
+ " SkPath::kC%sWDirection);\n", l, t, r, b, aCW ? "C" : "");
+ } else {
+ aXAlign = 5;
+ aYAlign = 5;
+ }
+ if (bShape) {
+ switch (bShape) {
+ case 1: // square
+ l = bXAlign * 10;
+ r = l + 20;
+ t = bYAlign * 10;
+ b = l + 20;
+ break;
+ case 2:
+ l = bXAlign * 10;
+ r = l + 20;
+ t = 10; b = 40;
+ bYAlign = 5;
+ break;
+ case 3:
+ l = 10; r = 40;
+ t = bYAlign * 10;
+ b = l + 20;
+ bXAlign = 5;
+ break;
+ }
+ path.addRect(l, t, r, b, bCW);
+ str += sprintf(str, " path.addRect(%d, %d, %d, %d,"
+ " SkPath::kC%sWDirection);\n", l, t, r, b, bCW ? "C" : "");
+ } else {
+ bXAlign = 5;
+ bYAlign = 5;
+ }
+ if (cShape) {
+ switch (cShape) {
+ case 1: // square
+ l = cXAlign * 6;
+ r = l + 12;
+ t = cYAlign * 6;
+ b = l + 12;
+ break;
+ case 2:
+ l = cXAlign * 6;
+ r = l + 12;
+ t = 20; b = 30;
+ cYAlign = 5;
+ break;
+ case 3:
+ l = 20; r = 30;
+ t = cYAlign * 6;
+ b = l + 20;
+ cXAlign = 5;
+ break;
+ }
+ path.addRect(l, t, r, b, cCW);
+ str += sprintf(str, " path.addRect(%d, %d, %d, %d,"
+ " SkPath::kC%sWDirection);\n", l, t, r, b, cCW ? "C" : "");
+ } else {
+ cXAlign = 5;
+ cYAlign = 5;
+ }
+ if (dShape) {
+ switch (dShape) {
+ case 1: // square
+ l = dXAlign * 4;
+ r = l + 9;
+ t = dYAlign * 4;
+ b = l + 9;
+ break;
+ case 2:
+ l = dXAlign * 6;
+ r = l + 9;
+ t = 32; b = 36;
+ dYAlign = 5;
+ break;
+ case 3:
+ l = 32; r = 36;
+ t = dYAlign * 6;
+ b = l + 9;
+ dXAlign = 5;
+ break;
+ }
+ path.addRect(l, t, r, b, dCW);
+ str += sprintf(str, " path.addRect(%d, %d, %d, %d,"
+ " SkPath::kC%sWDirection);\n", l, t, r, b, dCW ? "C" : "");
+ } else {
+ dXAlign = 5;
+ dYAlign = 5;
+ }
+ path.close();
+ outputProgress(state, pathStr, SkPath::kWinding_FillType);
+ testSimplifyx(path, false, out, state, pathStr);
+ state.testsRun++;
+ outputProgress(state, pathStr, SkPath::kEvenOdd_FillType);
+ testSimplifyx(path, true, out, state, pathStr);
+ state.testsRun++;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ } while (runNextTestSet(state));
+ return NULL;
+}
+
+void Simplify4x4RectsThreaded_Test(int& testsRun)
+{
+ SkDebugf("%s\n", __FUNCTION__);
+#ifdef SK_DEBUG
+ gDebugMaxWindSum = 4;
+ gDebugMaxWindValue = 4;
+#endif
+ const char testLineStr[] = "testLine";
+ initializeTests(testLineStr, sizeof(testLineStr));
+ int testsStart = testsRun;
+ for (int a = 0; a < 8; ++a) { // outermost
+ for (int b = a ; b < 8; ++b) {
+ for (int c = b ; c < 8; ++c) {
+ for (int d = c; d < 8; ++d) {
+ testsRun += dispatchTest4(testSimplify4x4RectsMain, a, b, c, d);
+ }
+ if (!gRunTestsInOneThread) SkDebugf(".");
+ }
+ if (!gRunTestsInOneThread) SkDebugf("%d", b);
+ }
+ if (!gRunTestsInOneThread) SkDebugf("\n%d", a);
+ }
+ testsRun += waitForCompletion();
+ SkDebugf("%s tests=%d total=%d\n", __FUNCTION__, testsRun - testsStart, testsRun);
+}
+
diff --git a/experimental/Intersection/SkAntiEdge.cpp b/experimental/Intersection/SkAntiEdge.cpp
new file mode 100644
index 0000000000..41887fe3c1
--- /dev/null
+++ b/experimental/Intersection/SkAntiEdge.cpp
@@ -0,0 +1,1082 @@
+/*
+ * SkAntiEdge.cpp
+ * core
+ *
+ * Created by Cary Clark on 5/6/11.
+ * Copyright 2011 __MyCompanyName__. All rights reserved.
+ *
+ */
+
+#include "SkAntiEdge.h"
+#include "SkPoint.h"
+
+void SkAntiEdge::pointOnLine(SkFixed x, SkFixed y) {
+ float x0 = SkFixedToFloat(x);
+ float y0 = SkFixedToFloat(y);
+ float x1 = SkFixedToFloat(fFirstX);
+ float y1 = SkFixedToFloat(fFirstY);
+ float x2 = SkFixedToFloat(fLastX);
+ float y2 = SkFixedToFloat(fLastY);
+ float numer = (x2 - x1) * (y1 - y0) - (x1 - x0) * (y2 - y1);
+ float denom = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
+ double dist = fabs(numer) / sqrt(denom);
+ SkAssertResult(dist < 0.01);
+}
+
+void SkAntiEdge::pointInLine(SkFixed x, SkFixed y) {
+ if (y == SK_MaxS32) {
+ return;
+ }
+ pointOnLine(x, y);
+ SkAssertResult(y >= fFirstY && y <= fLastY);
+}
+
+void SkAntiEdge::validate() {
+ pointOnLine(fWalkX, fY);
+ pointOnLine(fX, fWalkY);
+}
+
+bool SkAntiEdge::setLine(const SkPoint& p0, const SkPoint& p1) {
+ fFirstY = SkScalarToFixed(p0.fY);
+ fLastY = SkScalarToFixed(p1.fY);
+ if (fFirstY == fLastY) {
+ return false;
+ }
+ fFirstX = SkScalarToFixed(p0.fX);
+ fLastX = SkScalarToFixed(p1.fX);
+ if (fFirstY > fLastY) {
+ SkTSwap(fFirstX, fLastX);
+ SkTSwap(fFirstY, fLastY);
+ fWinding = -1;
+ } else {
+ fWinding = 1;
+ }
+ SkFixed dx = fLastX - fFirstX;
+ fDXFlipped = dx < 0;
+ SkFixed dy = fLastY - fFirstY;
+ fDX = SkFixedDiv(dx, dy);
+ fDY = dx == 0 ? SK_MaxS32 : SkFixedDiv(dy, SkFixedAbs(dx));
+ fLink = NULL;
+ fLinkSet = false;
+ return true;
+}
+
+void SkAntiEdge::calcLine() {
+ SkFixed yStartFrac = SkFixedFraction(fFirstY);
+ if (fDXFlipped) {
+ SkFixed vert = SK_Fixed1 - yStartFrac; // distance from y start to x-axis
+ fX0 = fFirstX + SkFixedMul(fDX, vert);
+ SkFixed backupX = fFirstX + SkFixedMul(vert, fDX); // x cell to back up to
+ SkFixed cellX = SkIntToFixed(SkFixedFloor(backupX));
+ SkFixed endX = SkIntToFixed(SkFixedFloor(fLastX));
+ if (cellX < endX) {
+ cellX = endX;
+ }
+ SkFixed distX = fFirstX - cellX; // to y-axis
+ fY0 = fFirstY + SkFixedMul(fDY, distX);
+ SkFixed rowBottom = SkIntToFixed(SkFixedCeil(fFirstY + 1));
+ if (fLastY > rowBottom) {
+ fPartialY = 0;
+ fX = fX0;
+ fY = rowBottom;
+ } else {
+ fPartialY = SkFixedFraction(fLastY);
+ fX = fLastX;
+ fY = fLastY;
+ }
+ } else {
+ fPartialY = yStartFrac;
+ fX0 = fFirstX - SkFixedMul(fDX, yStartFrac);
+ fY0 = fFirstY;
+ if (fDY != SK_MaxS32) {
+ SkFixed xStartFrac = SkFixedFraction(fFirstX);
+ fY0 -= SkFixedMul(fDY, xStartFrac);
+ }
+ fX = fFirstX;
+ fY = fFirstY;
+ }
+ fWalkX = fX;
+ fWalkY = fY;
+ fFinished = false;
+}
+
+static SkFixed SkFixedAddPin(SkFixed a, SkFixed b) {
+ SkFixed result = a + b;
+ if (((a ^ ~b) & (a ^ result)) >= 0) { // one positive, one negative
+ return result; // or all three same sign
+ }
+ return a < 0 ? -SK_FixedMax : SK_FixedMax;
+}
+
+// edge is increasing in x and y
+uint16_t SkAntiEdge::advanceX(SkFixed left) {
+ validate();
+ SkFixed x = SkFixedAddPin(fX0, fDX);
+ SkFixed wy = SkIntToFixed(SkFixedFloor(fWalkY + SK_Fixed1));
+ pointOnLine(x, wy);
+ SkFixed partial = SK_Fixed1 - fPartialY;
+ SkFixed bottomPartial = wy - fLastY;
+ if (bottomPartial > 0) {
+ partial -= bottomPartial;
+ }
+ if (x > fLastX) {
+ x = fLastX;
+ wy = fLastY;
+ }
+ uint16_t coverage;
+ if (left >= x) {
+ fFinished = true;
+ coverage = partial - 1; // walker is to the right of edge
+ } else {
+ SkFixed y = SkFixedAddPin(fY0, fDY);
+ SkFixed wx = SkIntToFixed(SkFixedFloor(fWalkX + SK_Fixed1));
+ if (fDY != SK_MaxS32) {
+ pointOnLine(wx, y);
+ }
+ if (y > fLastY) {
+ y = fLastY;
+ wx = fLastX;
+ }
+ bool topCorner = fWalkX <= fX;
+ bool bottomCorner = x <= wx;
+ bool halfPlane = !(topCorner ^ bottomCorner);
+ if (halfPlane) {
+ if (x - SkIntToFixed(SkFixedFloor(fX)) <= SK_Fixed1) {
+ coverage = ~((fX + x) >> 1); // avg of fx, fx+dx
+ fFinished = true;
+ if (x >= left + SK_Fixed1) {
+ fWalkX = wx;
+ fY = fY0 = y;
+ }
+ } else {
+ SkAssertResult(y - SkIntToFixed(SkFixedFloor(fY)) <= SK_Fixed1);
+ coverage = ((fY + y) >> 1);
+ fFinished = y == fLastY;
+ fWalkX = wx;
+ fY = fY0 = y;
+ }
+ coverage = coverage * partial >> 16;
+ } else if (topCorner) {
+ SkFixed xDiff = wx - fX;
+ SkAssertResult(xDiff >= 0);
+ SkAssertResult(xDiff <= SK_Fixed1);
+ SkFixed yDiff = y - fWalkY;
+ // This may be a very small negative number if error accumulates
+ // FIXME: for now, try setting it to zero in that case.
+ if (yDiff < 0) {
+ fX = fX0 = SkIntToFixed(SkFixedCeil(fX));
+ yDiff = 0;
+ }
+ SkAssertResult(yDiff >= 0);
+ SkAssertResult(yDiff <= SK_Fixed1);
+ int xCoverage = xDiff >> 1; // throw away 1 bit so multiply
+ int yCoverage = yDiff >> 1; // stays in range
+ int triangle = xCoverage * yCoverage; // 30 bits
+ SkFixed bottomPartial = y - fLastY;
+ fFinished = bottomPartial >= 0;
+ if (fFinished) {
+ yCoverage = bottomPartial >> 1;
+ xCoverage = (wx - fLastX) >> 1;
+ triangle -= xCoverage * yCoverage;
+ }
+ coverage = triangle >> 15;
+ fWalkX = wx;
+ fY = fY0 = y;
+ } else {
+ SkAssertResult(bottomCorner);
+ SkFixed xDiff = x - fWalkX;
+ SkAssertResult(xDiff >= 0);
+ SkAssertResult(xDiff <= SK_Fixed1);
+ SkFixed yDiff = wy - fY;
+ SkAssertResult(yDiff >= 0);
+ SkAssertResult(yDiff <= SK_Fixed1);
+ int xCoverage = xDiff >> 1; // throw away 1 bit so multiply
+ int yCoverage = yDiff >> 1; // stays in range
+ int triangle = xCoverage * yCoverage >> 15;
+ coverage = partial - 1 - triangle;
+ fFinished = true;
+ }
+ }
+ validate();
+ return coverage;
+}
+
+// edge is increasing in x, but decreasing in y
+uint16_t SkAntiEdge::advanceFlippedX(SkFixed left) {
+ validate();
+ SkFixed x = SkFixedAddPin(fX0, -fDX);
+ SkFixed wy = SkIntToFixed(SkFixedFloor(fWalkY - 1));
+ pointOnLine(x, wy);
+ SkFixed partial = fPartialY ? fPartialY : SK_Fixed1;
+ SkFixed topPartial = fFirstY - wy;
+ if (topPartial > 0) {
+ partial -= topPartial;
+ }
+ if (x > fFirstX) {
+ x = fFirstX;
+ wy = fFirstY;
+ }
+ uint16_t coverage;
+ if (left >= x) {
+ fFinished = true;
+ coverage = partial - 1; // walker is to the right of edge
+ } else {
+ SkFixed y = SkFixedAddPin(fY0, -fDY);
+ SkFixed wx = SkIntToFixed(SkFixedFloor(fWalkX + SK_Fixed1));
+ pointOnLine(wx, y);
+ if (y < fFirstY) {
+ y = fFirstY;
+ wx = fFirstX;
+ }
+ bool bottomCorner = fWalkX <= fX;
+ bool topCorner = x <= wx;
+ bool halfPlane = !(topCorner ^ bottomCorner);
+ if (halfPlane) {
+ if (x - SkIntToFixed(SkFixedFloor(fX)) <= SK_Fixed1) {
+ coverage = ~((fX + x) >> 1); // avg of fx, fx+dx
+ fFinished = true;
+ } else {
+ SkAssertResult(y - SkIntToFixed(SkFixedFloor(fY)) <= SK_Fixed1);
+ coverage = ~((fY + y) >> 1);
+ fFinished = y == fY;
+ fWalkX = wx;
+ fY = fY0 = y;
+ }
+ coverage = coverage * partial >> 16;
+ } else if (bottomCorner) {
+ SkFixed xDiff = wx - fX;
+ SkAssertResult(xDiff >= 0);
+ SkAssertResult(xDiff <= SK_Fixed1);
+ SkFixed yDiff = fWalkY - y;
+ SkAssertResult(yDiff >= 0);
+ SkAssertResult(yDiff <= SK_Fixed1);
+ int xCoverage = xDiff >> 1; // throw away 1 bit so multiply
+ int yCoverage = yDiff >> 1; // stays in range
+ int triangle = xCoverage * yCoverage; // 30 bits
+ SkFixed bottomPartial = fFirstY - y;
+ fFinished = bottomPartial >= 0;
+ if (fFinished) {
+ yCoverage = bottomPartial >> 1;
+ xCoverage = (wx - fFirstX) >> 1;
+ triangle -= xCoverage * yCoverage;
+ }
+ coverage = triangle >> 15;
+ fWalkX = wx;
+ fY = fY0 = y;
+ } else {
+ SkAssertResult(topCorner);
+ SkFixed xDiff = x - fWalkX;
+ SkAssertResult(xDiff >= 0);
+ SkAssertResult(xDiff <= SK_Fixed1);
+ SkFixed yDiff = fY - wy;
+ SkAssertResult(yDiff >= 0);
+ SkAssertResult(yDiff <= SK_Fixed1);
+ int xCoverage = xDiff >> 1; // throw away 1 bit so multiply
+ int yCoverage = yDiff >> 1; // stays in range
+ int triangle = xCoverage * yCoverage >> 15;
+ coverage = partial - 1 - triangle;
+ fFinished = true;
+ }
+ }
+ validate();
+ return coverage;
+}
+
+void SkAntiEdge::advanceY(SkFixed top) {
+ validate();
+ fX0 = SkFixedAddPin(fX0, fDX);
+ fPartialY = 0;
+ if (fDXFlipped) {
+ if (fX0 < fLastX) {
+ fWalkX = fX = fLastX;
+ } else {
+ fWalkX = fX = fX0;
+ }
+ SkFixed bottom = top + SK_Fixed1;
+ if (bottom > fLastY) {
+ bottom = fLastY;
+ }
+ SkFixed vert = bottom - fFirstY; // distance from y start to x-axis
+ SkFixed backupX = fFirstX + SkFixedMul(vert, fDX); // x cell to back up to
+ SkFixed distX = fFirstX - SkIntToFixed(SkFixedFloor(backupX)); // to y-axis
+ fY0 = fFirstY + SkFixedMul(fDY, distX);
+
+ fY = top + SK_Fixed1;
+ if (fY > fLastY) {
+ fY = fLastY;
+ }
+ if (fLastY < top + SK_Fixed1) {
+ fPartialY = SkFixedFraction(fLastY);
+ }
+ } else {
+ if (fX0 > fLastX) {
+ fX0 = fLastX;
+ }
+ fX = fX0;
+ }
+ fWalkY = SkIntToFixed(SkFixedFloor(fWalkY + SK_Fixed1));
+ if (fWalkY > fLastY) {
+ fWalkY = fLastY;
+ }
+ validate();
+ fFinished = false;
+}
+
+int SkAntiEdgeBuilder::build(const SkPoint pts[], int count) {
+ SkAntiEdge* edge = fEdges.append();
+ for (int index = 0; index < count; ++index) {
+ if (edge->setLine(pts[index], pts[(index + 1) % count])) {
+ edge = fEdges.append();
+ }
+ }
+ int result = fEdges.count();
+ fEdges.setCount(--result);
+ if (result > 0) {
+ sk_bzero(&fHeadEdge, sizeof(fHeadEdge));
+ sk_bzero(&fTailEdge, sizeof(fTailEdge));
+ for (int index = 0; index < result; ++index) {
+ *fList.append() = &fEdges[index];
+ }
+ }
+ return result;
+}
+
+void SkAntiEdgeBuilder::calc() {
+ for (SkAntiEdge* active = fEdges.begin(); active != fEdges.end(); ++active) {
+ active->calcLine();
+ }
+ // compute winding sum for edges
+ SkAntiEdge* first = fHeadEdge.fNext;
+ SkAntiEdge* active;
+ SkAntiEdge* listTop = first;
+ for (active = first; active != &fTailEdge; active = active->fNext) {
+ active->fWindingSum = active->fWinding;
+ while (listTop->fLastY < active->fFirstY) {
+ listTop = listTop->fNext;
+ }
+ for (SkAntiEdge* check = listTop; check->fFirstY <= active->fFirstY; check = check->fNext) {
+ if (check == active) {
+ continue;
+ }
+ if (check->fLastY <= active->fFirstY) {
+ continue;
+ }
+ if (check->fFirstX > active->fFirstX) {
+ continue;
+ }
+ if (check->fFirstX == active->fFirstX && check->fDX > active->fDX) {
+ continue;
+ }
+ active->fWindingSum += check->fWinding;
+ }
+ }
+}
+
+extern "C" {
+ static int edge_compare(const void* a, const void* b) {
+ const SkAntiEdge* edgea = *(const SkAntiEdge**)a;
+ const SkAntiEdge* edgeb = *(const SkAntiEdge**)b;
+
+ int valuea = edgea->fFirstY;
+ int valueb = edgeb->fFirstY;
+
+ if (valuea == valueb) {
+ valuea = edgea->fFirstX;
+ valueb = edgeb->fFirstX;
+ }
+
+ if (valuea == valueb) {
+ valuea = edgea->fDX;
+ valueb = edgeb->fDX;
+ }
+
+ return valuea - valueb;
+ }
+}
+
+void SkAntiEdgeBuilder::sort(SkTDArray<SkAntiEdge*>& listOfEdges) {
+ SkAntiEdge** list = listOfEdges.begin();
+ int count = listOfEdges.count();
+ qsort(list, count, sizeof(SkAntiEdge*), edge_compare);
+
+ // link the edges in sorted order
+ for (int i = 1; i < count; i++) {
+ list[i - 1]->fNext = list[i];
+ list[i]->fPrev = list[i - 1];
+ }
+}
+
+#define kEDGE_HEAD_XY SK_MinS32
+#define kEDGE_TAIL_XY SK_MaxS32
+
+void SkAntiEdgeBuilder::sort() {
+ sort(fList);
+ SkAntiEdge* last = fList.end()[-1];
+ fHeadEdge.fNext = fList[0];
+ fHeadEdge.fFirstX = fHeadEdge.fFirstY = fHeadEdge.fWalkY = fHeadEdge.fLastY = kEDGE_HEAD_XY;
+ fList[0]->fPrev = &fHeadEdge;
+
+ fTailEdge.fPrev = last;
+ fTailEdge.fFirstX = fTailEdge.fFirstY = fTailEdge.fWalkY = fTailEdge.fLastY = kEDGE_TAIL_XY;
+ last->fNext = &fTailEdge;
+}
+
+static inline void remove_edge(SkAntiEdge* edge) {
+ edge->fPrev->fNext = edge->fNext;
+ edge->fNext->fPrev = edge->fPrev;
+}
+
+static inline void swap_edges(SkAntiEdge* prev, SkAntiEdge* next) {
+ SkASSERT(prev->fNext == next && next->fPrev == prev);
+
+ // remove prev from the list
+ prev->fPrev->fNext = next;
+ next->fPrev = prev->fPrev;
+
+ // insert prev after next
+ prev->fNext = next->fNext;
+ next->fNext->fPrev = prev;
+ next->fNext = prev;
+ prev->fPrev = next;
+}
+
+static void backward_insert_edge_based_on_x(SkAntiEdge* edge SkDECLAREPARAM(int, y)) {
+ SkFixed x = edge->fFirstX;
+
+ for (;;) {
+ SkAntiEdge* prev = edge->fPrev;
+
+ // add 1 to curr_y since we may have added new edges (built from curves)
+ // that start on the next scanline
+ SkASSERT(prev && SkFixedFloor(prev->fWalkY - prev->fDXFlipped) <= y + 1);
+
+ if (prev->fFirstX <= x) {
+ break;
+ }
+ swap_edges(prev, edge);
+ }
+}
+
+static void insert_new_edges(SkAntiEdge* newEdge, SkFixed curr_y) {
+ int y = SkFixedFloor(curr_y);
+ if (SkFixedFloor(newEdge->fWalkY - newEdge->fDXFlipped) < y) {
+ return;
+ }
+ while (SkFixedFloor(newEdge->fWalkY - newEdge->fDXFlipped) == y) {
+ SkAntiEdge* next = newEdge->fNext;
+ backward_insert_edge_based_on_x(newEdge SkPARAM(y));
+ newEdge = next;
+ }
+}
+
+static int find_active_edges(int y, SkAntiEdge** activeLeft,
+ SkAntiEdge** activeLast) {
+ SkAntiEdge* first = *activeLeft;
+ SkFixed bottom = first->fLastY;
+ SkAntiEdge* active = first->fNext;
+ first->fLinkSet = false;
+ SkFixed yLimit = SkIntToFixed(y + 1); // limiting pixel edge
+ for ( ; active->fWalkY != kEDGE_TAIL_XY; active = active->fNext) {
+ active->fLinkSet = false;
+ if (yLimit <= active->fWalkY - active->fDXFlipped) {
+ break;
+ }
+ if ((*activeLeft)->fWalkX > active->fWalkX) {
+ *activeLeft = active;
+ }
+ if (bottom > active->fLastY) {
+ bottom = active->fLastY;
+ }
+ }
+ *activeLast = active;
+ return SkFixedCeil(bottom);
+}
+
+// All edges are oriented to increase in y. Link edges with common tops and
+// bottoms so the links can share their winding sum.
+void SkAntiEdgeBuilder::link() {
+ SkAntiEdge* tail = fEdges.end();
+ // look for links forwards and backwards
+ SkAntiEdge* prev = fEdges.begin();
+ SkAntiEdge* active;
+ for (active = prev + 1; active != tail; ++active) {
+ if (prev->fWinding == active->fWinding) {
+ if (prev->fLastX == active->fFirstX && prev->fLastY == active->fFirstY) {
+ prev->fLink = active;
+ active->fLinkSet = true;
+ } else if (active->fLastX == prev->fFirstX && active->fLastY == prev->fFirstY) {
+ active->fLink = prev;
+ prev->fLinkSet = true;
+ }
+ }
+ prev = active;
+ }
+ // look for stragglers
+ prev = fEdges.begin() - 1;
+ do {
+ do {
+ if (++prev == tail) {
+ return;
+ }
+ } while (prev->fLinkSet || NULL != prev->fLink);
+ for (active = prev + 1; active != tail; ++active) {
+ if (active->fLinkSet || NULL != active->fLink) {
+ continue;
+ }
+ if (prev->fWinding != active->fWinding) {
+ continue;
+ }
+ if (prev->fLastX == active->fFirstX && prev->fLastY == active->fFirstY) {
+ prev->fLink = active;
+ active->fLinkSet = true;
+ break;
+ }
+ if (active->fLastX == prev->fFirstX && active->fLastY == prev->fFirstY) {
+ active->fLink = prev;
+ prev->fLinkSet = true;
+ break;
+ }
+ }
+ } while (true);
+}
+
+void SkAntiEdgeBuilder::split(SkAntiEdge* edge, SkFixed y) {
+ SkPoint upperPoint = {edge->fFirstX, edge->fFirstY};
+ SkPoint midPoint = {edge->fFirstX + SkMulDiv(y - edge->fFirstY,
+ edge->fLastX - edge->fFirstX, edge->fLastY - edge->fFirstY), y};
+ SkPoint lowerPoint = {edge->fLastX, edge->fLastY};
+ int8_t winding = edge->fWinding;
+ edge->setLine(upperPoint, midPoint);
+ edge->fWinding = winding;
+ SkAntiEdge* lower = fEdges.append();
+ lower->setLine(midPoint, lowerPoint);
+ lower->fWinding = winding;
+ insert_new_edges(lower, y);
+}
+
+// An edge computes pixel coverage by considering the integral winding value
+// to its left. If an edge is enclosed by fractional winding, split it.
+// FIXME: This is also a good time to find crossing edges and split them, too.
+void SkAntiEdgeBuilder::split() {
+ // create a new set of edges that describe the whole link
+ SkTDArray<SkAntiEdge> links;
+ SkAntiEdge* first = fHeadEdge.fNext;
+ SkAntiEdge* active;
+ for (active = first; active != &fTailEdge; active = active->fNext) {
+ if (active->fLinkSet || NULL == active->fLink) {
+ continue;
+ }
+ SkAntiEdge* link = links.append();
+ link->fFirstX = active->fFirstX;
+ link->fFirstY = active->fFirstY;
+ SkAntiEdge* linkEnd;
+ SkAntiEdge* next = active;
+ do {
+ linkEnd = next;
+ next = next->fLink;
+ } while (NULL != next);
+ link->fLastX = linkEnd->fLastX;
+ link->fLastY = linkEnd->fLastY;
+ }
+ // create a list of all edges, links and singletons
+ SkTDArray<SkAntiEdge*> list;
+ for (active = links.begin(); active != links.end(); ++active) {
+ *list.append() = active;
+ }
+ for (active = first; active != &fTailEdge; active = active->fNext) {
+ if (!active->fLinkSet && NULL == active->fLink) {
+ SkAntiEdge* link = links.append();
+ link->fFirstX = active->fFirstX;
+ link->fFirstY = active->fFirstY;
+ link->fLastX = active->fLastX;
+ link->fLastY = active->fLastY;
+ *list.append() = link;
+ }
+ }
+ SkAntiEdge tail;
+ tail.fFirstY = tail.fLastY = kEDGE_TAIL_XY;
+ *list.append() = &tail;
+ sort(list);
+ // walk the list, splitting edges partially occluded on the left
+ SkAntiEdge* listTop = list[0];
+ for (active = first; active != &fTailEdge; active = active->fNext) {
+ while (listTop->fLastY < active->fFirstY) {
+ listTop = listTop->fNext;
+ }
+ for (SkAntiEdge* check = listTop; check->fFirstY < active->fLastY; check = check->fNext) {
+ if (check->fFirstX > active->fFirstX) {
+ continue;
+ }
+ if (check->fFirstX == active->fFirstX && check->fDX > active->fDX) {
+ continue;
+ }
+ if (check->fFirstY > active->fFirstY) {
+ split(active, check->fFirstY);
+ }
+ if (check->fLastY < active->fLastY) {
+ split(active, check->fLastY);
+ }
+ }
+ }
+}
+
+static inline uint8_t coverage_to_8(int coverage) {
+ uint16_t x = coverage < 0 ? 0 : coverage > 0xFFFF ? 0xFFFF : coverage;
+ // for values 0x7FFF and smaller, add (0x7F - high byte) and trunc
+ // for values 0x8000 and larger, subtract (high byte - 0x80) and trunc
+ return (x + 0x7f + (x >> 15) - (x >> 8)) >> 8;
+}
+
+void SkAntiEdgeBuilder::walk(uint8_t* result, int rowBytes, int height) {
+ SkAntiEdge* first = fHeadEdge.fNext;
+ SkFixed top = first->fWalkY - first->fDXFlipped;
+ int y = SkFixedFloor(top);
+ do {
+ SkAntiEdge* activeLeft = first;
+ SkAntiEdge* activeLast, * active;
+ int yLast = find_active_edges(y, &activeLeft, &activeLast);
+ while (y < yLast) {
+ SkAssertResult(y >= 0);
+ SkAssertResult(y < height);
+ SkFixed left = activeLeft->fWalkX;
+ int x = SkFixedFloor(left);
+ uint8_t* resultPtr = &result[y * rowBytes + x];
+ bool finished;
+ do {
+ left = SkIntToFixed(x);
+ SkAssertResult(x >= 0);
+ // SkAssertResult(x < pixelCol);
+ if (x >= rowBytes) { // FIXME: cumulative error in fX += fDX
+ break; // fails to set fFinished early enough
+ } // see test 6 (dy<dx)
+ finished = true;
+ int coverage = 0;
+ for (active = first; active != activeLast; active = active->fNext) {
+ if (left + SK_Fixed1 <= active->fX) {
+ finished = false;
+ continue; // walker is to the left of edge
+ }
+ int cover = active->fDXFlipped ?
+ active->advanceFlippedX(left) : active->advanceX(left);
+ if (0 == active->fWindingSum) {
+ cover = -cover;
+ }
+ coverage += cover;
+ finished &= active->fFinished;
+ }
+ uint8_t old = *resultPtr;
+ uint8_t pix = coverage_to_8(coverage);
+ uint8_t blend = old > pix ? old : pix;
+ *resultPtr++ = blend;
+ ++x;
+ } while (!finished);
+ ++y;
+ top = SkIntToFixed(y);
+ SkFixed topLimit = top + SK_Fixed1;
+ SkFixed xSort = -SK_FixedMax;
+ for (active = first; active != activeLast; active = active->fNext) {
+ if (xSort > active->fX || topLimit > active->fLastY) {
+ yLast = y; // recompute bottom after all Ys are advanced
+ }
+ xSort = active->fX;
+ if (active->fWalkY < active->fLastY) {
+ active->advanceY(top);
+ }
+ }
+ for (active = first; active != activeLast; ) {
+ SkAntiEdge* next = active->fNext;
+ if (top >= active->fLastY) {
+ remove_edge(active);
+ }
+ active = next;
+ }
+ first = fHeadEdge.fNext;
+ }
+ SkAntiEdge* prev = activeLast->fPrev;
+ if (prev != &fHeadEdge) {
+ insert_new_edges(prev, top);
+ first = fHeadEdge.fNext;
+ }
+ } while (first->fWalkY < kEDGE_TAIL_XY);
+}
+
+void SkAntiEdgeBuilder::process(const SkPoint* points, int ptCount,
+ uint8_t* result, int pixelCol, int pixelRow) {
+ if (ptCount < 3) {
+ return;
+ }
+ int count = build(points, ptCount);
+ if (count == 0) {
+ return;
+ }
+ SkAssertResult(count > 1);
+ link();
+ sort();
+ split();
+ calc();
+ walk(result, pixelCol, pixelRow);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+int test3by3_test;
+
+// input is a rectangle
+static void test_3_by_3() {
+ const int pixelRow = 3;
+ const int pixelCol = 3;
+ const int ptCount = 4;
+ const int pixelCount = pixelRow * pixelCol;
+ const SkPoint tests[][ptCount] = {
+ {{2.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 2.0f}, {2.0f, 2.0f}}, // 0: full rect
+ {{2.5f, 1.0f}, {1.5f, 1.0f}, {1.5f, 2.0f}, {2.5f, 2.0f}}, // 1: y edge
+ {{2.0f, 1.5f}, {1.0f, 1.5f}, {1.0f, 2.5f}, {2.0f, 2.5f}}, // 2: x edge
+ {{2.5f, 1.5f}, {1.5f, 1.5f}, {1.5f, 2.5f}, {2.5f, 2.5f}}, // 3: x/y edge
+ {{2.8f, 0.2f}, {0.2f, 0.2f}, {0.2f, 2.8f}, {2.8f, 2.8f}}, // 4: large
+ {{1.8f, 1.2f}, {1.2f, 1.2f}, {1.2f, 1.8f}, {1.8f, 1.8f}}, // 5: small
+ {{0.0f, 0.0f}, {0.0f, 1.0f}, {3.0f, 2.0f}, {3.0f, 1.0f}}, // 6: dy<dx
+ {{3.0f, 0.0f}, {0.0f, 1.0f}, {0.0f, 2.0f}, {3.0f, 1.0f}}, // 7: dy<-dx
+ {{1.0f, 0.0f}, {0.0f, 0.0f}, {1.0f, 3.0f}, {2.0f, 3.0f}}, // 8: dy>dx
+ {{2.0f, 0.0f}, {1.0f, 0.0f}, {0.0f, 3.0f}, {1.0f, 3.0f}}, // 9: dy>-dx
+ {{0.5f, 0.5f}, {0.5f, 1.5f}, {2.5f, 2.5f}, {2.5f, 1.5f}}, // 10: dy<dx 2
+ {{2.5f, 0.5f}, {0.5f, 1.5f}, {0.5f, 2.5f}, {2.5f, 1.5f}}, // 11: dy<-dx 2
+ {{0.0f, 0.0f}, {2.0f, 0.0f}, {2.0f, 2.0f}, {0.0f, 2.0f}}, // 12: 2x2
+ {{0.0f, 0.0f}, {3.0f, 0.0f}, {3.0f, 3.0f}, {0.0f, 3.0f}}, // 13: 3x3
+ {{1.75f, 0.25f}, {2.75f, 1.25f}, {1.25f, 2.75f}, {0.25f, 1.75f}}, // 14
+ {{2.25f, 0.25f}, {2.75f, 0.75f}, {0.75f, 2.75f}, {0.25f, 2.25f}}, // 15
+ {{0.25f, 0.75f}, {0.75f, 0.25f}, {2.75f, 2.25f}, {2.25f, 2.75f}}, // 16
+ {{1.25f, 0.50f}, {1.75f, 0.25f}, {2.75f, 2.25f}, {2.25f, 2.50f}}, // 17
+ {{1.00f, 0.75f}, {2.00f, 0.50f}, {2.00f, 1.50f}, {1.00f, 1.75f}}, // 18
+ {{1.00f, 0.50f}, {2.00f, 0.75f}, {2.00f, 1.75f}, {1.00f, 1.50f}}, // 19
+ {{1.00f, 0.75f}, {1.00f, 1.75f}, {2.00f, 1.50f}, {2.00f, 0.50f}}, // 20
+ {{1.00f, 0.50f}, {1.00f, 1.50f}, {2.00f, 1.75f}, {2.00f, 0.75f}}, // 21
+ };
+ const uint8_t results[][pixelCount] = {
+ {0x00, 0x00, 0x00, // 0: 1 pixel rect
+ 0x00, 0xFF, 0x00,
+ 0x00, 0x00, 0x00},
+ {0x00, 0x00, 0x00, // 1: y edge
+ 0x00, 0x7F, 0x80,
+ 0x00, 0x00, 0x00},
+ {0x00, 0x00, 0x00, // 2: x edge
+ 0x00, 0x7F, 0x00,
+ 0x00, 0x7F, 0x00},
+ {0x00, 0x00, 0x00, // 3: x/y edge
+ 0x00, 0x40, 0x40,
+ 0x00, 0x40, 0x40},
+ {0xA3, 0xCC, 0xA3, // 4: large
+ 0xCC, 0xFF, 0xCC,
+ 0xA3, 0xCC, 0xA3},
+ {0x00, 0x00, 0x00, // 5: small
+ 0x00, 0x5C, 0x00,
+ 0x00, 0x00, 0x00},
+ {0xD5, 0x80, 0x2B, // 6: dy<dx
+ 0x2A, 0x7F, 0xD4,
+ 0x00, 0x00, 0x00},
+ {0x2B, 0x80, 0xD5, // 7: dy<-dx
+ 0xD4, 0x7F, 0x2A,
+ 0x00, 0x00, 0x00},
+ {0xD5, 0x2A, 0x00, // 8: dy>dx
+ 0x80, 0x7F, 0x00,
+ 0x2B, 0xD4, 0x00},
+ {0x2A, 0xD5, 0x00, // 9: dy>-dx
+ 0x7F, 0x80, 0x00,
+ 0xD4, 0x2B, 0x00},
+ {0x30, 0x10, 0x00, // 10: dy<dx 2
+ 0x50, 0xDF, 0x50,
+ 0x00, 0x10, 0x30},
+ {0x00, 0x10, 0x30, // 11: dy<-dx 2
+ 0x50, 0xDF, 0x50,
+ 0x30, 0x10, 0x00},
+ {0xFF, 0xFF, 0x00, // 12: 2x2
+ 0xFF, 0xFF, 0x00,
+ 0x00, 0x00, 0x00},
+ {0xFF, 0xFF, 0xFF, // 13: 3x3
+ 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF},
+ {0x00, 0x70, 0x20, // 14
+ 0x70, 0xFF, 0x70,
+ 0x20, 0x70, 0x00},
+ {0x00, 0x20, 0x60, // 15
+ 0x20, 0xBF, 0x20,
+ 0x60, 0x20, 0x00},
+ {0x60, 0x20, 0x00, // 16
+ 0x20, 0xBF, 0x20,
+ 0x00, 0x20, 0x60},
+ {0x00, 0x60, 0x04, // 17
+ 0x00, 0x40, 0x60,
+ 0x00, 0x00, 0x3C},
+ {0x00, 0x60, 0x00, // 18
+ 0x00, 0x9F, 0x00,
+ 0x00, 0x00, 0x00},
+ {0x00, 0x60, 0x00, // 19
+ 0x00, 0x9F, 0x00,
+ 0x00, 0x00, 0x00},
+ {0x00, 0x60, 0x00, // 20
+ 0x00, 0x9F, 0x00,
+ 0x00, 0x00, 0x00},
+ {0x00, 0x60, 0x00, // 21
+ 0x00, 0x9F, 0x00,
+ 0x00, 0x00, 0x00},
+ };
+ const int testCount = sizeof(tests) / sizeof(tests[0]);
+ SkAssertResult(testCount == sizeof(results) / sizeof(results[0]));
+ int testFirst = test3by3_test < 0 ? 0 : test3by3_test;
+ int testLast = test3by3_test < 0 ? testCount : test3by3_test + 1;
+ for (int testIndex = testFirst; testIndex < testLast; ++testIndex) {
+ uint8_t result[pixelRow][pixelCol];
+ sk_bzero(result, sizeof(result));
+ const SkPoint* rect = tests[testIndex];
+ SkAntiEdgeBuilder builder;
+ builder.process(rect, ptCount, result[0], pixelCol, pixelRow);
+ SkAssertResult(memcmp(results[testIndex], result[0], pixelCount) == 0);
+ }
+}
+
+// input has arbitrary number of points
+static void test_arbitrary_3_by_3() {
+ const int pixelRow = 3;
+ const int pixelCol = 3;
+ const int pixelCount = pixelRow * pixelCol;
+ const SkPoint t1[] = { {1,1}, {2,1}, {2,1.5f}, {1,1.5f}, {1,2}, {2,2},
+ {2,1.5f}, {1,1.5f}, {1,1} };
+ const SkPoint* tests[] = { t1 };
+ size_t testPts[] = { sizeof(t1) / sizeof(t1[0]) };
+ const uint8_t results[][pixelCount] = {
+ {0x00, 0x00, 0x00, // 0: 1 pixel rect
+ 0x00, 0xFF, 0x00,
+ 0x00, 0x00, 0x00},
+ };
+ const int testCount = sizeof(tests) / sizeof(tests[0]);
+ SkAssertResult(testCount == sizeof(results) / sizeof(results[0]));
+ int testFirst = test3by3_test < 0 ? 0 : test3by3_test;
+ int testLast = test3by3_test < 0 ? testCount : test3by3_test + 1;
+ for (int testIndex = testFirst; testIndex < testLast; ++testIndex) {
+ uint8_t result[pixelRow][pixelCol];
+ sk_bzero(result, sizeof(result));
+ const SkPoint* pts = tests[testIndex];
+ size_t ptCount = testPts[testIndex];
+ SkAntiEdgeBuilder builder;
+ builder.process(pts, ptCount, result[0], pixelCol, pixelRow);
+ SkAssertResult(memcmp(results[testIndex], result[0], pixelCount) == 0);
+ }
+}
+
+#include "SkRect.h"
+#include "SkPath.h"
+
+int testsweep_test;
+
+static void create_sweep(uint8_t* result, int pixelRow, int pixelCol, SkScalar rectWidth) {
+ const int ptCount = 4;
+ SkRect refRect = {pixelCol / 2 - rectWidth / 2, 5,
+ pixelCol / 2 + rectWidth / 2, pixelRow / 2 - 5};
+ SkPath refPath;
+ refPath.addRect(refRect);
+ SkScalar angleFirst = testsweep_test < 0 ? 0 : testsweep_test;
+ SkScalar angleLast = testsweep_test < 0 ? 360 : testsweep_test + 1;
+ for (SkScalar angle = angleFirst; angle < angleLast; angle += 12) {
+ SkPath rotPath;
+ SkMatrix matrix;
+ matrix.setRotate(angle, SkIntToScalar(pixelCol) / 2,
+ SkIntToScalar(pixelRow) / 2);
+ refPath.transform(matrix, &rotPath);
+ SkPoint rect[ptCount], temp[2];
+ SkPath::Iter iter(rotPath, false);
+ int index = 0;
+ for (;;) {
+ SkPath::Verb verb = iter.next(temp);
+ if (verb == SkPath::kMove_Verb) {
+ continue;
+ }
+ if (verb == SkPath::kClose_Verb) {
+ break;
+ }
+ SkAssertResult(SkPath::kLine_Verb == verb);
+ rect[index++] = temp[0];
+ }
+ SkAntiEdgeBuilder builder;
+ builder.process(rect, ptCount, result, pixelCol, pixelRow);
+ }
+}
+
+static void create_horz(uint8_t* result, int pixelRow, int pixelCol) {
+ const int ptCount = 4;
+ for (SkScalar x = 0; x < 100; x += 5) {
+ SkPoint rect[ptCount];
+ rect[0].fX = 0; rect[0].fY = x;
+ rect[1].fX = 100; rect[1].fY = x;
+ rect[2].fX = 100; rect[2].fY = x + x / 50;
+ rect[3].fX = 0; rect[3].fY = x + x / 50;
+ SkAntiEdgeBuilder builder;
+ builder.process(rect, ptCount, result, pixelCol, pixelRow);
+ }
+}
+
+static void create_vert(uint8_t* result, int pixelRow, int pixelCol) {
+ const int ptCount = 4;
+ for (SkScalar x = 0; x < 100; x += 5) {
+ SkPoint rect[ptCount];
+ rect[0].fY = 0; rect[0].fX = x;
+ rect[1].fY = 100; rect[1].fX = x;
+ rect[2].fY = 100; rect[2].fX = x + x / 50;
+ rect[3].fY = 0; rect[3].fX = x + x / 50;
+ SkAntiEdgeBuilder builder;
+ builder.process(rect, ptCount, result, pixelCol, pixelRow);
+ }
+}
+
+static void create_angle(uint8_t* result, int pixelRow, int pixelCol, SkScalar angle) {
+ const int ptCount = 4;
+ SkRect refRect = {25, 25, 125, 125};
+ SkPath refPath;
+ for (SkScalar x = 30; x < 125; x += 5) {
+ refRect.fTop = x;
+ refRect.fBottom = x + (x - 25) / 50;
+ refPath.addRect(refRect);
+ }
+ SkPath rotPath;
+ SkMatrix matrix;
+ matrix.setRotate(angle, 75, 75);
+ refPath.transform(matrix, &rotPath);
+ SkPath::Iter iter(rotPath, false);
+ for (SkScalar x = 30; x < 125; x += 5) {
+ SkPoint rect[ptCount], temp[2];
+ int index = 0;
+ for (;;) {
+ SkPath::Verb verb = iter.next(temp);
+ if (verb == SkPath::kMove_Verb) {
+ continue;
+ }
+ if (verb == SkPath::kClose_Verb) {
+ break;
+ }
+ SkAssertResult(SkPath::kLine_Verb == verb);
+ rect[index++] = temp[0];
+ }
+ // if ((x == 30 || x == 75) && angle == 12) continue;
+ SkAntiEdgeBuilder builder;
+ builder.process(rect, ptCount, result, pixelCol, pixelRow);
+ }
+}
+
+static void test_sweep() {
+ const int pixelRow = 100;
+ const int pixelCol = 100;
+ uint8_t result[pixelRow][pixelCol];
+ sk_bzero(result, sizeof(result));
+ create_sweep(result[0], pixelRow, pixelCol, 1);
+}
+
+static void test_horz() {
+ const int pixelRow = 100;
+ const int pixelCol = 100;
+ uint8_t result[pixelRow][pixelCol];
+ sk_bzero(result, sizeof(result));
+ create_horz(result[0], pixelRow, pixelCol);
+}
+
+static void test_vert() {
+ const int pixelRow = 100;
+ const int pixelCol = 100;
+ uint8_t result[pixelRow][pixelCol];
+ sk_bzero(result, sizeof(result));
+ create_vert(result[0], pixelRow, pixelCol);
+}
+
+static void test_angle(SkScalar angle) {
+ const int pixelRow = 150;
+ const int pixelCol = 150;
+ uint8_t result[pixelRow][pixelCol];
+ sk_bzero(result, sizeof(result));
+ create_angle(result[0], pixelRow, pixelCol, angle);
+}
+
+#include "SkBitmap.h"
+
+void CreateSweep(SkBitmap* sweep, SkScalar rectWidth) {
+ const int pixelRow = 100;
+ const int pixelCol = 100;
+ sweep->setConfig(SkBitmap::kA8_Config, pixelCol, pixelRow);
+ sweep->allocPixels();
+ sweep->eraseColor(SK_ColorTRANSPARENT);
+ sweep->lockPixels();
+ void* pixels = sweep->getPixels();
+ create_sweep((uint8_t*) pixels, pixelRow, pixelCol, rectWidth);
+ sweep->unlockPixels();
+}
+
+void CreateHorz(SkBitmap* sweep) {
+ const int pixelRow = 100;
+ const int pixelCol = 100;
+ sweep->setConfig(SkBitmap::kA8_Config, pixelCol, pixelRow);
+ sweep->allocPixels();
+ sweep->eraseColor(SK_ColorTRANSPARENT);
+ sweep->lockPixels();
+ void* pixels = sweep->getPixels();
+ create_horz((uint8_t*) pixels, pixelRow, pixelCol);
+ sweep->unlockPixels();
+}
+
+void CreateVert(SkBitmap* sweep) {
+ const int pixelRow = 100;
+ const int pixelCol = 100;
+ sweep->setConfig(SkBitmap::kA8_Config, pixelCol, pixelRow);
+ sweep->allocPixels();
+ sweep->eraseColor(SK_ColorTRANSPARENT);
+ sweep->lockPixels();
+ void* pixels = sweep->getPixels();
+ create_vert((uint8_t*) pixels, pixelRow, pixelCol);
+ sweep->unlockPixels();
+}
+
+void CreateAngle(SkBitmap* sweep, SkScalar angle) {
+ const int pixelRow = 150;
+ const int pixelCol = 150;
+ sweep->setConfig(SkBitmap::kA8_Config, pixelCol, pixelRow);
+ sweep->allocPixels();
+ sweep->eraseColor(SK_ColorTRANSPARENT);
+ sweep->lockPixels();
+ void* pixels = sweep->getPixels();
+ create_angle((uint8_t*) pixels, pixelRow, pixelCol, angle);
+ sweep->unlockPixels();
+}
+
+#include "SkCanvas.h"
+
+static void testPng() {
+ SkBitmap device;
+ device.setConfig(SkBitmap::kARGB_8888_Config, 4, 4);
+ device.allocPixels();
+ device.eraseColor(0xFFFFFFFF);
+
+ SkCanvas canvas(device);
+ canvas.drawARGB(167, 0, 0, 0);
+
+ device.lockPixels();
+ unsigned char* pixels = (unsigned char*) device.getPixels();
+ SkDebugf("%02x%02x%02x%02x", pixels[3], pixels[2], pixels[1], pixels[0]);
+}
+
+void SkAntiEdge_Test() {
+ testPng();
+ test_arbitrary_3_by_3();
+ test_angle(12);
+#if 0
+ test3by3_test = 18;
+#else
+ test3by3_test = -1;
+#endif
+#if 0
+ testsweep_test = 7 * 12;
+#else
+ testsweep_test = -1;
+#endif
+ if (testsweep_test == -1) {
+ test_3_by_3();
+ }
+ test_sweep();
+ test_horz();
+ test_vert();
+}
+
diff --git a/experimental/Intersection/as.htm b/experimental/Intersection/as.htm
new file mode 100644
index 0000000000..43dceb4757
--- /dev/null
+++ b/experimental/Intersection/as.htm
@@ -0,0 +1,805 @@
+<html>
+<head>
+<div style="height:0">
+
+<div id="quad56">
+debugShowActiveSpans id=1 (366.608826,151.196014 378.803101,136.674606 398.164948,136.674606) t=0.490456543 (380.294495,140.44487) other=7 otherT=0.578520747 otherIndex=3 windSum=? windValue=1
+debugShowActiveSpans id=2 (398.164948,136.674606 354.009216,208.816208) t=0 (398.164948,136.674606) other=1 otherT=1 otherIndex=4 windSum=? windValue=1
+debugShowActiveSpans id=3 (354.009216,208.816208 393.291473,102.232819) t=0 (354.009216,208.816208) other=2 otherT=1 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=3 (354.009216,208.816208 393.291473,102.232819) t=0.508945199 (374.00174,154.571106) other=6 otherT=0.598402499 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=3 (354.009216,208.816208 393.291473,102.232819) t=0.634079491 (378.917297,141.233871) other=7 otherT=0.536512741 otherIndex=1 windSum=-1 windValue=1
+debugShowActiveSpans id=5 (359.978058,136.581512 378.315979,136.581512 388.322723,149.613556) t=0.597488996 (378.917297,141.233856) other=7 otherT=0.536512973 otherIndex=2 windSum=? windValue=1
+debugShowActiveSpans id=6 (388.322723,149.613556 364.390686,157.898193) t=0 (388.322723,149.613556) other=5 otherT=1 otherIndex=4 windSum=? windValue=1
+debugShowActiveSpans id=6 (388.322723,149.613556 364.390686,157.898193) t=0.598402499 (374.00174,154.571106) other=3 otherT=0.508945199 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=7 (364.390686,157.898193 375.281769,136.674606 396.039917,136.674606) t=0 (364.390686,157.898193) other=6 otherT=1 otherIndex=2 windSum=? windValue=1
+debugShowActiveSpans id=7 (364.390686,157.898193 375.281769,136.674606 396.039917,136.674606) t=0.536512741 (378.917297,141.233871) other=3 otherT=0.634079491 otherIndex=2 windSum=? windValue=1
+debugShowActiveSpans id=7 (364.390686,157.898193 375.281769,136.674606 396.039917,136.674606) t=0.536512973 (378.917297,141.233856) other=5 otherT=0.597488996 otherIndex=3 windSum=-1 windValue=1
+</div>
+
+<div id="quad57c">
+debugShowActiveSpans id=1 (303.12088,141.299606 330.463562,217.659027) t=0.845414865 (326.236786,205.854996) other=13 otherT=0.999999913 otherIndex=3 windSum=? windValue=1
+debugShowActiveSpans id=2 (330.463562,217.659027 358.606506,141.299606) t=-0 (330.463562,217.659027) other=1 otherT=1 otherIndex=3 windSum=? windValue=1
+debugShowActiveSpans id=2 (330.463562,217.659027 358.606506,141.299606) t=0.154585135 (334.814056,205.854996) other=13 otherT=0.812241055 otherIndex=2 windSum=? windValue=1
+debugShowActiveSpans id=2 (330.463562,217.659027 358.606506,141.299606) t=0.283842806 (338.451721,195.984955) other=16 otherT=0.363593784 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=4 (362.874634,159.705902 335.665344,233.397751) t=0.379109438 (352.559326,187.643173) other=17 otherT=0.412818074 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=13 (371.919067,205.854996 326.236786,205.854996) t=0.812241055 (334.814056,205.854996) other=2 otherT=0.154585135 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=14 (326.236786,205.854996 329.104431,231.663818 351.512085,231.663818) t=0.962138429 (349.843323,231.626816) other=15 otherT=0.0583966647 otherIndex=1 windSum=1 windValue=1
+debugShowActiveSpans id=15 (351.512085,231.663818 322.935669,231.030273) t=0 (351.512085,231.663818) other=14 otherT=1 otherIndex=3 windSum=1 windValue=1
+debugShowActiveSpans id=16 (326.837006,195.984955 358.78125,195.984955) t=0 (326.837006,195.984955) other=18 otherT=1 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=16 (326.837006,195.984955 358.78125,195.984955) t=0.363593784 (338.451721,195.984955) other=2 otherT=0.283842806 otherIndex=2 windSum=? windValue=1
+debugShowActiveSpans id=16 (326.837006,195.984955 358.78125,195.984955) t=0.708806554 (349.479309,195.984955) other=4 otherT=0.492307539 otherIndex=3 windSum=? windValue=1
+debugShowActiveSpans id=17 (358.78125,195.984955 343.709442,175.778046) t=-0 (358.78125,195.984955) other=16 otherT=1 otherIndex=3 windSum=? windValue=1
+debugShowActiveSpans id=17 (358.78125,195.984955 343.709442,175.778046) t=0.412818074 (352.559326,187.643173) other=4 otherT=0.379109438 otherIndex=2 windSum=? windValue=1
+debugShowActiveSpans id=17 (358.78125,195.984955 343.709442,175.778046) t=0.902761903 (345.174988,177.74292) other=2 otherT=0.522739691 otherIndex=3 windSum=? windValue=1
+debugShowActiveSpans id=18 (343.709442,175.778046 328.570923,175.778046 326.837006,195.984955) t=0 (343.709442,175.778046) other=17 otherT=1 otherIndex=3 windSum=? windValue=1
+</div>
+
+<div id="quad57b">
+debugShowActiveSpans id=1 (303.12088,141.299606 330.463562,217.659027) t=0.845414865 (326.236786,205.854996) other=13 otherT=0.999999913 otherIndex=3 windSum=1 windValue=1
+debugShowActiveSpans id=2 (330.463562,217.659027 358.606506,141.299606) t=-0 (330.463562,217.659027) other=1 otherT=1 otherIndex=3 windSum=? windValue=1
+debugShowActiveSpans id=2 (330.463562,217.659027 358.606506,141.299606) t=0.154585135 (334.814056,205.854996) other=13 otherT=0.812241055 otherIndex=2 windSum=? windValue=1
+debugShowActiveSpans id=2 (330.463562,217.659027 358.606506,141.299606) t=0.283842806 (338.451721,195.984955) other=16 otherT=0.363593784 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=4 (362.874634,159.705902 335.665344,233.397751) t=0.379109438 (352.559326,187.643173) other=17 otherT=0.412818074 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=13 (371.919067,205.854996 326.236786,205.854996) t=0.812241055 (334.814056,205.854996) other=2 otherT=0.154585135 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=16 (326.837006,195.984955 358.78125,195.984955) t=0 (326.837006,195.984955) other=18 otherT=1 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=16 (326.837006,195.984955 358.78125,195.984955) t=0.363593784 (338.451721,195.984955) other=2 otherT=0.283842806 otherIndex=2 windSum=? windValue=1
+debugShowActiveSpans id=16 (326.837006,195.984955 358.78125,195.984955) t=0.708806554 (349.479309,195.984955) other=4 otherT=0.492307539 otherIndex=3 windSum=? windValue=1
+debugShowActiveSpans id=17 (358.78125,195.984955 343.709442,175.778046) t=-0 (358.78125,195.984955) other=16 otherT=1 otherIndex=3 windSum=? windValue=1
+debugShowActiveSpans id=17 (358.78125,195.984955 343.709442,175.778046) t=0.412818074 (352.559326,187.643173) other=4 otherT=0.379109438 otherIndex=2 windSum=? windValue=1
+debugShowActiveSpans id=17 (358.78125,195.984955 343.709442,175.778046) t=0.902761903 (345.174988,177.74292) other=2 otherT=0.522739691 otherIndex=3 windSum=? windValue=1
+debugShowActiveSpans id=18 (343.709442,175.778046 328.570923,175.778046 326.837006,195.984955) t=0 (343.709442,175.778046) other=17 otherT=1 otherIndex=3 windSum=? windValue=1
+</div>
+
+<div id="quad57a">
+debugShowActiveSpans id=2 (330.463562,217.659027 358.606506,141.299606) t=0.154585135 (334.814056,205.854996) other=13 otherT=0.812241055 otherIndex=2 windSum=-1 windValue=1
+debugShowActiveSpans id=2 (330.463562,217.659027 358.606506,141.299606) t=0.283842806 (338.451721,195.984955) other=16 otherT=0.363593784 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=4 (362.874634,159.705902 335.665344,233.397751) t=0.379109438 (352.559326,187.643173) other=17 otherT=0.412818074 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=16 (326.837006,195.984955 358.78125,195.984955) t=0 (326.837006,195.984955) other=18 otherT=1 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=16 (326.837006,195.984955 358.78125,195.984955) t=0.363593784 (338.451721,195.984955) other=2 otherT=0.283842806 otherIndex=2 windSum=? windValue=1
+debugShowActiveSpans id=16 (326.837006,195.984955 358.78125,195.984955) t=0.708806554 (349.479309,195.984955) other=4 otherT=0.492307539 otherIndex=3 windSum=? windValue=1
+debugShowActiveSpans id=17 (358.78125,195.984955 343.709442,175.778046) t=-0 (358.78125,195.984955) other=16 otherT=1 otherIndex=3 windSum=? windValue=1
+debugShowActiveSpans id=17 (358.78125,195.984955 343.709442,175.778046) t=0.412818074 (352.559326,187.643173) other=4 otherT=0.379109438 otherIndex=2 windSum=? windValue=1
+debugShowActiveSpans id=17 (358.78125,195.984955 343.709442,175.778046) t=0.902761903 (345.174988,177.74292) other=2 otherT=0.522739691 otherIndex=3 windSum=? windValue=1
+debugShowActiveSpans id=18 (343.709442,175.778046 328.570923,175.778046 326.837006,195.984955) t=0 (343.709442,175.778046) other=17 otherT=1 otherIndex=3 windSum=? windValue=1
+</div>
+
+<div id="quad58b">
+debugShowActiveSpans id=3 (303.12088,141.299606 330.463562,217.659027) t=0.845414865 (326.236786,205.854996) other=16 otherT=0.999999913 otherIndex=3 windSum=1 windValue=1
+debugShowActiveSpans id=4 (330.463562,217.659027 358.606506,141.299606) t=-0 (330.463562,217.659027) other=3 otherT=1 otherIndex=3 windSum=? windValue=1
+debugShowActiveSpans id=4 (330.463562,217.659027 358.606506,141.299606) t=0.154585135 (334.814056,205.854996) other=16 otherT=0.812241055 otherIndex=2 windSum=? windValue=1
+debugShowActiveSpans id=4 (330.463562,217.659027 358.606506,141.299606) t=0.283842806 (338.451721,195.984955) other=19 otherT=0.363593784 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=6 (362.874634,159.705902 335.665344,233.397751) t=0.287405665 (355.054535,180.885361) other=20 otherT=0.497256785 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=16 (371.919067,205.854996 326.236786,205.854996) t=0.812241055 (334.814056,205.854996) other=4 otherT=0.154585135 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=19 (326.837006,195.984955 358.78125,195.984955) t=0 (326.837006,195.984955) other=21 otherT=1 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=19 (326.837006,195.984955 358.78125,195.984955) t=0.363593784 (338.451721,195.984955) other=4 otherT=0.283842806 otherIndex=2 windSum=? windValue=1
+debugShowActiveSpans id=19 (326.837006,195.984955 358.78125,195.984955) t=0.708806554 (349.479309,195.984955) other=6 otherT=0.492307539 otherIndex=3 windSum=? windValue=1
+debugShowActiveSpans id=20 (358.78125,195.984955 358.78125,175.778046 343.709442,175.778046) t=0 (358.78125,195.984955) other=19 otherT=1 otherIndex=3 windSum=? windValue=1
+debugShowActiveSpans id=20 (358.78125,195.984955 358.78125,175.778046 343.709442,175.778046) t=0.497256785 (355.054535,180.885361) other=6 otherT=0.287405665 otherIndex=2 windSum=? windValue=1
+debugShowActiveSpans id=20 (358.78125,195.984955 358.78125,175.778046 343.709442,175.778046) t=0.925970638 (345.858368,175.888794) other=4 otherT=0.547021444 otherIndex=3 windSum=? windValue=1
+debugShowActiveSpans id=21 (343.709442,175.778046 328.570923,175.778046 326.837006,195.984955) t=0 (343.709442,175.778046) other=20 otherT=1 otherIndex=3 windSum=? windValue=1
+</div>
+
+<div id="quad58a">
+debugShowActiveSpans id=4 (330.463562,217.659027 358.606506,141.299606) t=0.154585135 (334.814056,205.854996) other=16 otherT=0.812241055 otherIndex=2 windSum=-1 windValue=1
+debugShowActiveSpans id=4 (330.463562,217.659027 358.606506,141.299606) t=0.283842806 (338.451721,195.984955) other=19 otherT=0.363593784 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=6 (362.874634,159.705902 335.665344,233.397751) t=0.287405665 (355.054535,180.885361) other=20 otherT=0.497256785 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=19 (326.837006,195.984955 358.78125,195.984955) t=0 (326.837006,195.984955) other=21 otherT=1 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=19 (326.837006,195.984955 358.78125,195.984955) t=0.363593784 (338.451721,195.984955) other=4 otherT=0.283842806 otherIndex=2 windSum=? windValue=1
+debugShowActiveSpans id=19 (326.837006,195.984955 358.78125,195.984955) t=0.708806554 (349.479309,195.984955) other=6 otherT=0.492307539 otherIndex=3 windSum=? windValue=1
+debugShowActiveSpans id=20 (358.78125,195.984955 358.78125,175.778046 343.709442,175.778046) t=0 (358.78125,195.984955) other=19 otherT=1 otherIndex=3 windSum=? windValue=1
+debugShowActiveSpans id=20 (358.78125,195.984955 358.78125,175.778046 343.709442,175.778046) t=0.497256785 (355.054535,180.885361) other=6 otherT=0.287405665 otherIndex=2 windSum=? windValue=1
+debugShowActiveSpans id=20 (358.78125,195.984955 358.78125,175.778046 343.709442,175.778046) t=0.925970638 (345.858368,175.888794) other=4 otherT=0.547021444 otherIndex=3 windSum=? windValue=1
+debugShowActiveSpans id=21 (343.709442,175.778046 328.570923,175.778046 326.837006,195.984955) t=0 (343.709442,175.778046) other=20 otherT=1 otherIndex=3 windSum=? windValue=1
+</div>
+
+<div id="quad59">
+debugShowQuadIntersection wtTs[0]=0 (369.863983,145.645813 382.380371,121.254936 406.236359,121.254936) (369.863983,145.645813) wnTs[0]=1 (406.236359,121.254936 409.445679,121.254936 412.975952,121.789818) (412.975952,121.789818)
+debugShowQuadLineIntersection wtTs[0]=1 (369.863983,145.645813 382.380371,121.254936 406.236359,121.254936) (406.236359,121.254936) wnTs[0]=0 (412.975952,121.789818 369.863983,145.645813) (412.975952,121.789818)
+debugShowQuadLineIntersection wtTs[0]=0 (406.236359,121.254936 409.445679,121.254936 412.975952,121.789818) (406.236359,121.254936) wnTs[0]=1 (412.975952,121.789818 369.863983,145.645813) (369.863983,145.645813)
+debugShowQuadIntersection no intersect (369.863983,145.645813 382.380371,121.254936 406.236359,121.254936) (369.970581,137.94342 383.98465,121.254936 406.235992,121.254936)
+debugShowQuadIntersection no intersect (369.863983,145.645813 382.380371,121.254936 406.236359,121.254936) (406.235992,121.254936 425.705902,121.254936 439.71994,137.087616)
+debugShowQuadLineIntersection wtTs[0]=0.934062756 (369.863983,145.645813 382.380371,121.254936 406.236359,121.254936) (403.139679,121.360977) wnTs[0]=0.17423 (439.71994,137.087616 369.970581,137.94342) (427.567535,137.236725)
+debugShowQuadIntersection wtTs[0]=9.61225644e-05 (406.236359,121.254936 409.445679,121.254936 412.975952,121.789818) (406.236969,121.254936) wnTs[0]=0.000495996 (406.235992,121.254936 425.705902,121.254936 439.71994,137.087616) (406.25531,121.254944)
+debugShowQuadLineIntersection no intersect (369.970581,137.94342 383.98465,121.254936 406.235992,121.254936) (412.975952,121.789818 369.863983,145.645813)
+debugShowQuadLineIntersection no intersect (406.235992,121.254936 425.705902,121.254936 439.71994,137.087616) (412.975952,121.789818 369.863983,145.645813)
+debugShowQuadIntersection wtTs[0]=0 (369.970581,137.94342 383.98465,121.254936 406.235992,121.254936) (369.970581,137.94342) wnTs[0]=1 (406.235992,121.254936 425.705902,121.254936 439.71994,137.087616) (439.71994,137.087616)
+debugShowQuadLineIntersection wtTs[0]=1 (369.970581,137.94342 383.98465,121.254936 406.235992,121.254936) (406.235992,121.254936) wnTs[0]=0 (439.71994,137.087616 369.970581,137.94342) (439.71994,137.087616)
+debugShowQuadLineIntersection wtTs[0]=0 (406.235992,121.254936 425.705902,121.254936 439.71994,137.087616) (406.235992,121.254936) wnTs[0]=1 (439.71994,137.087616 369.970581,137.94342) (369.970581,137.94342)
+</div>
+
+<div id="quad59b">
+debugShowActiveSpans id=1 (369.863983,145.645813 382.380371,121.254936 406.236359,121.254936) t=0 (369.863983,145.645813) other=3 otherT=1 otherIndex=2 windSum=? windValue=1
+debugShowActiveSpans id=1 (369.863983,145.645813 382.380371,121.254936 406.236359,121.254936) t=0.174229721 (374.569672,137.886993) other=6 otherT=0.934062756 otherIndex=2 windSum=? windValue=1
+debugShowActiveSpans id=2 (406.236359,121.254936 409.445679,121.254936 412.975952,121.789818) t=0 (406.236359,121.254936) other=1 otherT=1 otherIndex=2 windSum=? windValue=1
+debugShowActiveSpans id=2 (406.236359,121.254936 409.445679,121.254936 412.975952,121.789818) t=0.000495995847 (406.239532,121.254936) other=5 otherT=9.61225644e-05 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=3 (412.975952,121.789818 369.863983,145.645813) t=0 (412.975952,121.789818) other=2 otherT=1 otherIndex=2 windSum=? windValue=1
+debugShowActiveSpans id=3 (412.975952,121.789818 369.863983,145.645813) t=0.669864243 (384.096771,137.770096) other=6 otherT=0.797471908 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=4 (369.970581,137.94342 383.98465,121.254936 406.235992,121.254936) t=0 (369.970581,137.94342) other=6 otherT=1 otherIndex=3 windSum=? windValue=1
+debugShowActiveSpans id=5 (406.235992,121.254936 425.705902,121.254936 439.71994,137.087616) t=0 (406.235992,121.254936) other=4 otherT=1 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=5 (406.235992,121.254936 425.705902,121.254936 439.71994,137.087616) t=9.61225644e-05 (406.239746,121.254936) other=2 otherT=0.000495995847 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=6 (439.71994,137.087616 369.970581,137.94342) t=0 (439.71994,137.087616) other=5 otherT=1 otherIndex=2 windSum=? windValue=1
+debugShowActiveSpans id=6 (439.71994,137.087616 369.970581,137.94342) t=0.797471908 (384.096771,137.770096) other=3 otherT=0.669864243 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=6 (439.71994,137.087616 369.970581,137.94342) t=0.934062756 (374.569672,137.886993) other=1 otherT=0.174229721 otherIndex=1 windSum=? windValue=1
+</div>
+
+<div id="quad60">
+debugShowActiveSpans id=1 (360.416077,166.795715 370.126831,147.872162 388.635406,147.872162) t=0 (360.416077,166.795715) other=2 otherT=1 otherIndex=2 windSum=? windValue=1
+debugShowActiveSpans id=2 (388.635406,147.872162 360.416077,166.795715) t=0 (388.635406,147.872162) other=1 otherT=1 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=2 (388.635406,147.872162 360.416077,166.795715) t=0.00925761141 (388.374176,148.047348) other=4 otherT=0.883679938 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=3 (353.2948,194.351074 353.2948,173.767563 364.167572,160.819855) t=0 (353.2948,194.351074) other=5 otherT=1 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=4 (364.167572,160.819855 375.040314,147.872162 392.303894,147.872162) t=0 (364.167572,160.819855) other=3 otherT=1 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=4 (364.167572,160.819855 375.040314,147.872162 392.303894,147.872162) t=0.883679938 (388.374176,148.047348) other=2 otherT=0.00925761141 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=5 (392.303894,147.872162 353.2948,194.351074) t=0 (392.303894,147.872162) other=4 otherT=1 otherIndex=2 windSum=? windValue=1
+</div>
+
+<div id="quad61">
+debugShowActiveSpans id=1 (348.781738,123.864815 369.848602,123.864815) t=0 (348.781738,123.864815) other=4 otherT=1 otherIndex=4 windSum=? windValue=1
+debugShowActiveSpans id=2 (369.848602,123.864815 369.848602,145.680267) t=0 (369.848602,123.864815) other=1 otherT=1 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=3 (369.848602,145.680267 382.360413,121.298294 406.207703,121.298294) t=0 (369.848602,145.680267) other=2 otherT=1 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=3 (369.848602,145.680267 382.360413,121.298294 406.207703,121.298294) t=0.258355433 (377.070221,134.709274) other=6 otherT=0.8038997 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=3 (369.848602,145.680267 382.360413,121.298294 406.207703,121.298294) t=0.914354357 (402.206024,121.477142) other=4 otherT=0.0696842495 otherIndex=2 windSum=? windValue=1
+debugShowActiveSpans id=3 (369.848602,145.680267 382.360413,121.298294 406.207703,121.298294) t=0.999082394 (406.16394,121.298317) other=5 otherT=0.998890674 otherIndex=2 windSum=? windValue=1
+debugShowActiveSpans id=4 (406.207703,121.298294 348.781738,123.864815) t=1.0265934e-07 (406.207703,121.298294) other=5 otherT=0.999874327 otherIndex=3 windSum=? windValue=1
+debugShowActiveSpans id=4 (406.207703,121.298294 348.781738,123.864815) t=0.0696842495 (402.206024,121.477142) other=3 otherT=0.914354357 otherIndex=2 windSum=? windValue=1
+debugShowActiveSpans id=4 (406.207703,121.298294 348.781738,123.864815) t=0.0881931013 (401.143127,121.524643) other=5 otherT=0.883517581 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=5 (369.961151,137.980698 383.970093,121.298294 406.213287,121.298294) t=0 (369.961151,137.980698) other=6 otherT=1 otherIndex=2 windSum=? windValue=1
+debugShowActiveSpans id=5 (369.961151,137.980698 383.970093,121.298294 406.213287,121.298294) t=0.883517581 (401.143127,121.524643) other=4 otherT=0.0881931013 otherIndex=3 windSum=? windValue=1
+debugShowActiveSpans id=5 (369.961151,137.980698 383.970093,121.298294 406.213287,121.298294) t=0.998890674 (406.16394,121.298317) other=3 otherT=0.999082394 otherIndex=3 windSum=? windValue=1
+debugShowActiveSpans id=5 (369.961151,137.980698 383.970093,121.298294 406.213287,121.298294) t=0.999874327 (406.207703,121.298294) other=4 otherT=1.0265934e-07 otherIndex=1 windSum=? windValue=1
+debugShowActiveSpans id=6 (406.213287,121.298294 369.961151,137.980698) t=0 (406.213287,121.298294) other=5 otherT=1 otherIndex=4 windSum=? windValue=1
+debugShowActiveSpans id=6 (406.213287,121.298294 369.961151,137.980698) t=0.8038997 (377.070221,134.709274) other=3 otherT=0.258355433 otherIndex=1 windSum=? windValue=1
+</div>
+
+<div id="quad61b">
+debugShowQuadLineIntersection wtTs[0]=0.914354357 (369.848602,145.680267 382.360413,121.298294 406.207703,121.298294) (402.206024,121.477142) wtTs[1]=1 (406.207703,121.298294) wnTs[0]=0.0696842 (406.207703,121.298294 348.781738,123.864815) (402.206024,121.477142) wnTs[1]=0 (406.207703,121.298294)
+debugShowQuadIntersection wtTs[0]=0.999082394 (369.848602,145.680267 382.360413,121.298294 406.207703,121.298294) (406.16394,121.298317) wnTs[0]=0.998891 (369.961151,137.980698 383.970093,121.298294 406.213287,121.298294) (406.16394,121.298317)
+debugShowQuadLineIntersection wtTs[0]=0.258355433 (369.848602,145.680267 382.360413,121.298294 406.207703,121.298294) (377.070221,134.709274) wnTs[0]=0.8039 (406.213287,121.298294 369.961151,137.980698) (377.070221,134.709274)
+debugShowQuadLineIntersection wtTs[0]=0.883517581 (369.961151,137.980698 383.970093,121.298294 406.213287,121.298294) (401.143127,121.524643) wtTs[1]=0.999874327 (406.207703,121.298294) wnTs[0]=0.0881931 (406.207703,121.298294 348.781738,123.864815) (401.143127,121.524643) wnTs[1]=1.0265934e-07 (406.207703,121.298294)
+debugShowQuadLineIntersection wtTs[0]=0 (369.961151,137.980698 383.970093,121.298294 406.213287,121.298294) (369.961151,137.980698) wtTs[1]=1 (406.213287,121.298294) wnTs[0]=1 (406.213287,121.298294 369.961151,137.980698) (369.961151,137.980698) wnTs[1]=0 (406.213287,121.298294)
+</div>
+
+<div id="quad61c">
+debugShowActiveSpans id=3 (369.848602,145.680267 382.360413,121.298294 406.207703,121.298294) t=0.914354357 (402.206024,121.477142) other=4 otherT=0.0696842495 otherIndex=2 windSum=-1 windValue=1
+debugShowActiveSpans id=4 (406.207703,121.298294 348.781738,123.864815) t=1.0265934e-07 (406.207703,121.298294) other=5 otherT=0.999874327 otherIndex=3 windSum=-1 windValue=1
+debugShowActiveSpans id=5 (369.961151,137.980698 383.970093,121.298294 406.213287,121.298294) t=0.998890674 (406.16394,121.298317) other=3 otherT=0.999082394 otherIndex=3 windSum=-1 windValue=1
+</div>
+
+</div>
+
+<script type="text/javascript">
+
+var testDivs = [
+ quad61,
+ quad61b,
+ quad61c,
+ quad60,
+ quad59b,
+ quad59,
+ quad58b,
+ quad58a,
+ quad57c,
+ quad57b,
+ quad57a,
+ quad56,
+];
+
+var decimal_places = 3; // make this 3 to show more precision
+
+var tests = [];
+var testTitles = [];
+var testIndex = 0;
+var ctx;
+
+var xmin;
+var ymin;
+var scale;
+var mouseX, mouseY;
+var srcLeft, srcTop;
+var srcWidth, srcHeight;
+var screenWidth, screenHeight;
+var drawnPts, drawnLines, drawnQuads, deferredLines, deferredQuads;
+
+var ptLabels = true;
+var digit_mode = false;
+var index_mode = true;
+var index_bits = -1;
+var debug_xy = false;
+var info_mode = false;
+var intersect_mode = false;
+var sequence = -1;
+
+var SPAN_ID = 1;
+var SPAN_X1 = 2;
+var SPAN_Y1 = 3;
+var SPAN_X2 = 4;
+var SPAN_Y2 = 5;
+var SPAN_L_T = 6;
+var SPAN_L_TX = 7;
+var SPAN_L_TY = 8;
+var SPAN_L_OTHER = 9;
+var SPAN_L_OTHERT = 10;
+var SPAN_L_OTHERI = 11;
+var SPAN_L_SUM = 12;
+var SPAN_L_VAL = 13;
+
+var SPAN_X3 = 6;
+var SPAN_Y3 = 7;
+var SPAN_Q_T = 8;
+var SPAN_Q_TX = 9;
+var SPAN_Q_TY = 10;
+var SPAN_Q_OTHER = 11;
+var SPAN_Q_OTHERT = 12;
+var SPAN_Q_OTHERI = 13;
+var SPAN_Q_SUM = 14;
+var SPAN_Q_VAL = 15;
+
+var ACTIVE_LINE_SPAN = 1;
+var ACTIVE_QUAD_SPAN = 2;
+var INTERSECT_QUAD_LINE = 3;
+var INTERSECT_QUAD_LINE_2 = 4;
+var INTERSECT_QUAD_LINE_NO = 5;
+var INTERSECT_QUAD = 6;
+var INTERSECT_QUAD_2 = 7;
+var INTERSECT_QUAD_NO = 8;
+
+function strs_to_nums(strs) {
+ var result = [];
+ for (var idx in strs) {
+ var str = strs[idx];
+ var num = parseFloat(str);
+ if (isNaN(num)) {
+ result.push(str);
+ } else {
+ result.push(num);
+ }
+ }
+ return result;
+}
+
+function construct_regexp(pattern) {
+ var escape = pattern.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
+ escape = escape.replace(/PT_VAL/g, "(\\d+\\.?\\d*),(\\d+\\.?\\d*)");
+ escape = escape.replace(/T_VAL/g, "(\\d+\\.?\\d*e?-?\\d*)");
+ escape = escape.replace(/IDX/g, "(\\d+)");
+ escape = escape.replace(/OPT/g, "(\\?|-?\\d+)");
+ return new RegExp(escape, 'i');
+}
+
+function parse_debugShowActiveSpans(test, title) {
+ var re_quad = construct_regexp(" id=IDX (PT_VAL PT_VAL PT_VAL) t=T_VAL (PT_VAL) other=IDX otherT=T_VAL otherIndex=IDX windSum=OPT windValue=IDX");
+ var re_line = construct_regexp(" id=IDX (PT_VAL PT_VAL) t=T_VAL (PT_VAL) other=IDX otherT=T_VAL otherIndex=IDX windSum=OPT windValue=IDX");
+
+ var strs = test.split("debugShowActiveSpans");
+ if (strs.length == 1)
+ return;
+ var spans = [];
+ for (var s in strs) {
+ var str = strs[s];
+ if (str == "\n") {
+ continue;
+ }
+ if (re_line.test(str)) {
+ var lineStrs = re_line.exec(str);
+ var line = strs_to_nums(lineStrs);
+ spans.push(ACTIVE_LINE_SPAN);
+ spans.push(line);
+ } else if (re_quad.test(str)) {
+ var quadStrs = re_quad.exec(str);
+ var quad = strs_to_nums(quadStrs);
+ spans.push(ACTIVE_QUAD_SPAN);
+ spans.push(quad);
+ }
+ }
+ if (spans.length >= 1) {
+ tests.push(spans);
+ testTitles.push(title);
+ }
+}
+
+function filter_str_by(id, str, regex, array) {
+ if (regex.test(str)) {
+ var strs = regex.exec(str);
+ var result = strs_to_nums(strs);
+ array.push(id);
+ array.push(result);
+ }
+}
+
+function parse_intersections(test, title) {
+ var re_quad_line = construct_regexp(" wtTs[0]=T_VAL (PT_VAL PT_VAL PT_VAL) (PT_VAL) wnTs[0]=T_VAL (PT_VAL PT_VAL) (PT_VAL)");
+ var re_quad_line_2 = construct_regexp(" wtTs[0]=T_VAL (PT_VAL PT_VAL PT_VAL) (PT_VAL) wtTs[1]=T_VAL (PT_VAL) wnTs[0]=T_VAL (PT_VAL PT_VAL) (PT_VAL) wnTs[1]=T_VAL (PT_VAL)");
+ var re_quad_line_no_intersect = construct_regexp(" no intersect (PT_VAL PT_VAL PT_VAL) (PT_VAL PT_VAL)");
+ var re_quad = construct_regexp(" wtTs[0]=T_VAL (PT_VAL PT_VAL PT_VAL) (PT_VAL) wnTs[0]=T_VAL (PT_VAL PT_VAL PT_VAL) (PT_VAL)");
+ var re_quad_2 = construct_regexp(" wtTs[0]=T_VAL (PT_VAL PT_VAL PT_VAL) (PT_VAL) wtTs[1]=T_VAL wnTs[0]=T_VAL (PT_VAL PT_VAL PT_VAL) (PT_VAL) wnTs[1]=T_VAL");
+ var re_quad_no_intersect = construct_regexp(" no intersect (PT_VAL PT_VAL PT_VAL) (PT_VAL PT_VAL PT_VAL)");
+
+ var strs = test.split(/debugShow[A-Za-z]+Intersection/);
+ if (strs.length == 1)
+ return;
+ var spans = [];
+ for (var s in strs) {
+ var str = strs[s];
+ if (str == "\n") {
+ continue;
+ }
+ filter_str_by(INTERSECT_QUAD_LINE, str, re_quad_line, spans);
+ filter_str_by(INTERSECT_QUAD_LINE_2, str, re_quad_line_2, spans);
+ filter_str_by(INTERSECT_QUAD_LINE_NO, str, re_quad_line_no_intersect, spans);
+ filter_str_by(INTERSECT_QUAD, str, re_quad, spans);
+ filter_str_by(INTERSECT_QUAD_2, str, re_quad_2, spans);
+ filter_str_by(INTERSECT_QUAD_NO, str, re_quad_no_intersect, spans);
+ }
+ if (spans.length >= 1) {
+ tests.push(spans);
+ testTitles.push(title);
+ }
+}
+
+function init(test) {
+ var canvas = document.getElementById('canvas');
+ if (!canvas.getContext) return;
+ screenWidth = canvas.width = window.innerWidth - 20;
+ screenHeight = canvas.height = window.innerHeight - 20;
+ ctx = canvas.getContext('2d');
+ xmin = Infinity;
+ var xmax = -Infinity;
+ ymin = Infinity;
+ var ymax = -Infinity;
+ var scanType = -1;
+ for (var scansStr in test) {
+ var scans = parseInt(scansStr);
+ var scan = test[scans];
+ if (scanType == -1) {
+ scanType = scan;
+ continue;
+ }
+ if (scanType == ACTIVE_LINE_SPAN || scanType == ACTIVE_QUAD_SPAN) {
+ var last = scanType == ACTIVE_QUAD_SPAN ? SPAN_X3 : SPAN_X2;
+ for (var idx = SPAN_X1; idx <= last; idx += 2) {
+ xmin = Math.min(xmin, scan[idx]);
+ xmax = Math.max(xmax, scan[idx]);
+ ymin = Math.min(ymin, scan[idx + 1]);
+ ymax = Math.max(ymax, scan[idx + 1]);
+ }
+ } else {
+ var start = 1;
+ if (scanType != INTERSECT_QUAD_LINE_NO && scanType != INTERSECT_QUAD_NO) {
+ start = 2;
+ }
+ for (var idx = start; idx < start + 6; idx += 2) {
+ xmin = Math.min(xmin, scan[idx]);
+ xmax = Math.max(xmax, scan[idx]);
+ ymin = Math.min(ymin, scan[idx + 1]);
+ ymax = Math.max(ymax, scan[idx + 1]);
+ }
+ start = start + 6;
+ if (scanType == INTERSECT_QUAD_LINE || scanType == INTERSECT_QUAD) {
+ start += 3;
+ }
+ if (scanType == INTERSECT_QUAD_LINE_2) {
+ start += 6;
+ }
+ if (scanType == INTERSECT_QUAD_2) {
+ start += 4;
+ }
+ var end = start + 4;
+ if (scanType == INTERSECT_QUAD || scanType == INTERSECT_QUAD_2 || scanType == INTERSECT_QUAD_NO) {
+ end += 2;
+ }
+ for (var idx = start; idx < end; idx += 2) {
+ xmin = Math.min(xmin, scan[idx]);
+ xmax = Math.max(xmax, scan[idx]);
+ ymin = Math.min(ymin, scan[idx + 1]);
+ ymax = Math.max(ymax, scan[idx + 1]);
+ }
+ }
+ scanType = -1;
+ }
+ srcWidth = xmax - xmin;
+ srcHeight = ymax - ymin;
+ var hscale = ctx.canvas.width / srcWidth;
+ var vscale = ctx.canvas.height / srcHeight;
+ var minScale = Math.min(hscale, vscale);
+ scale = minScale;
+ srcLeft = xmin;
+ srcTop = ymin;
+}
+
+function drawPoint(px, py) {
+ for (var pts = 0; pts < drawnPts.length; pts += 2) {
+ var x = drawnPts[pts];
+ var y = drawnPts[pts + 1];
+ if (px == x && py == y) {
+ return;
+ }
+ }
+ drawnPts.push(px);
+ drawnPts.push(py);
+ var label = px.toFixed(decimal_places) + ", " + py.toFixed(decimal_places);
+ var _px = (px - srcLeft)* scale;
+ var _py = (py - srcTop) * scale;
+ ctx.beginPath();
+ ctx.arc(_px, _py, 3, 0, Math.PI*2, true);
+ ctx.closePath();
+ ctx.fill();
+ ctx.fillText(label, _px + 5, _py);
+}
+
+function drawLine(x1, y1, x2, y2, selected) {
+ for (var pts = 0; pts < drawnLines.length; pts += 4) {
+ if (x1 == drawnLines[pts] && y1 == drawnLines[pts + 1]) {
+ return;
+ }
+ if (x2 == drawnLines[pts + 2] && x2 == drawnLines[pts + 3]) {
+ return;
+ }
+ }
+ if (selected) {
+ drawnLines.push(x1);
+ drawnLines.push(y1);
+ drawnLines.push(x2);
+ drawnLines.push(y2);
+ ctx.beginPath();
+ ctx.moveTo((x1 - srcLeft) * scale,
+ (y1 - srcTop) * scale);
+ ctx.lineTo((x2 - srcLeft) * scale,
+ (y2 - srcTop) * scale);
+ ctx.stroke();
+ return;
+ }
+ deferredLines.push(x1);
+ deferredLines.push(y1);
+ deferredLines.push(x2);
+ deferredLines.push(y2);
+}
+
+function drawQuad(x1, y1, x2, y2, x3, y3, selected) {
+ for (var pts = 0; pts < drawnQuads.length; pts += 6) {
+ if (x1 == drawnQuads[pts] && y1 == drawnQuads[pts + 1]) {
+ return;
+ }
+ if (x2 == drawnQuads[pts + 2] && x2 == drawnQuads[pts + 3]) {
+ return;
+ }
+ if (x2 == drawnQuads[pts + 4] && x2 == drawnQuads[pts + 5]) {
+ return;
+ }
+ }
+ if (selected) {
+ drawnQuads.push(x1);
+ drawnQuads.push(y1);
+ drawnQuads.push(x2);
+ drawnQuads.push(y2);
+ drawnQuads.push(x3);
+ drawnQuads.push(y3);
+ ctx.beginPath();
+ ctx.moveTo((x1 - srcLeft) * scale,
+ (y1 - srcTop) * scale);
+ ctx.quadraticCurveTo((x2 - srcLeft) * scale,
+ (y2 - srcTop) * scale,
+ (x3 - srcLeft) * scale,
+ (y3 - srcTop) * scale);
+ ctx.stroke();
+ return;
+ }
+ deferredQuads.push(x1);
+ deferredQuads.push(y1);
+ deferredQuads.push(x2);
+ deferredQuads.push(y2);
+ deferredQuads.push(x3);
+ deferredQuads.push(y3);
+}
+
+function drawDeferred() {
+ for (var pts = 0; pts < deferredQuads.length; pts += 6) {
+ drawQuad(deferredQuads[pts], deferredQuads[pts + 1],
+ deferredQuads[pts + 2], deferredQuads[pts + 3],
+ deferredQuads[pts + 4], deferredQuads[pts + 5], true);
+ }
+ for (var pts = 0; pts < deferredLines.length; pts += 4) {
+ drawLine(deferredLines[pts], deferredLines[pts + 1],
+ deferredLines[pts + 2], deferredLines[pts + 3], true);
+ }
+}
+
+function draw(test, title) {
+ ctx.fillStyle = "rgba(0,0,0, 0.1)";
+ ctx.font = "normal 50px Arial";
+ ctx.fillText(title, 50, 50);
+ ctx.font = "normal 10px Arial";
+ ctx.lineWidth = "1.001"; "0.999";
+ var curId = -1;
+ var firstIdx;
+ var lastIdx;
+ var index_tst = -1;
+ drawnPts = [];
+ drawnLines = [];
+ drawnQuads = [];
+ deferredLines = [];
+ deferredQuads = [];
+ var scanType = -1;
+ var partIndex = 0;
+ for (var scansStr in test) {
+ var scans = parseInt(scansStr);
+ var scan = test[scans];
+ if (scanType == -1) {
+ scanType = scan;
+ continue;
+ }
+ partIndex++;
+ var hasId = scanType == ACTIVE_LINE_SPAN || scanType == ACTIVE_QUAD_SPAN ? SPAN_ID : -1;
+ if (hasId >= 0 && curId != scan[hasId]) {
+ curId = hasId;
+ firstIdx = lastIdx = partIndex;
+ index_tst++;
+ var nextIdx = lastIdx + 1;
+ while (nextIdx * 2 < test.length && test[nextIdx * 2][hasId] == curId) {
+ lastIdx = nextIdx++;
+ }
+ } else if (hasId < 0) {
+ firstIdx = lastIdx = partIndex;
+ index_tst++;
+ }
+ var seq = sequence % (test.length / 2);
+ var selected = sequence >= 0 ? seq == partIndex
+ : (index_bits & (1 << index_tst)) != 0 && partIndex == firstIdx;
+ var skippable = (sequence >= 0 && seq >= firstIdx && seq <= lastIdx)
+ || partIndex != firstIdx;
+ if (skippable && !selected) {
+ scanType = -1;
+ continue;
+ }
+ var types = [scanType == ACTIVE_LINE_SPAN ? 0 : 1];
+ var pts = [];
+ if (scanType == ACTIVE_LINE_SPAN || scanType == ACTIVE_QUAD_SPAN) {
+ pts.push(SPAN_X1);
+ } else {
+ pts.push(scanType != INTERSECT_QUAD_NO && scanType != INTERSECT_QUAD_LINE_NO ? 2 : 1);
+ types.push(scanType == INTERSECT_QUAD_NO || scanType == INTERSECT_QUAD || scanType == INTERSECT_QUAD_2 ? 1 : 0);
+ pts.push(scanType == INTERSECT_QUAD_LINE || scanType == INTERSECT_QUAD ? 11
+ : scanType == INTERSECT_QUAD_LINE_2 ? 14 : scanType == INTERSECT_QUAD_2 ? 12 : 7);
+ }
+ ctx.strokeStyle = "red";
+ for (var typeIndex = 0; typeIndex < types.length; ++typeIndex) {
+ var type = types[typeIndex];
+ var index = pts[typeIndex];
+ if (type == 1) {
+ drawQuad(scan[index + 0], scan[index + 1], scan[index + 2], scan[index + 3],
+ scan[index + 4], scan[index + 5], selected);
+ } else {
+ drawLine(scan[index + 0], scan[index + 1], scan[index + 2], scan[index + 3],
+ selected);
+ }
+ }
+ if (debug_xy && selected) {
+ var debugText = "";
+ for (var typeIndex = 0; typeIndex < types.length; ++typeIndex) {
+ var type = types[typeIndex];
+ var index = pts[typeIndex];
+ for (var idx = pts[typeIndex]; idx < (type == 1 ? pts[typeIndex] + 6 : pts[typeIndex] + 4); idx += 2) {
+ var screenX = (scan[idx] - srcLeft) * scale;
+ var screenY = (scan[idx + 1] - srcTop) * scale;
+ debugText += screenX.toFixed(decimal_places) + ", ";
+ debugText += screenY.toFixed(decimal_places) + " ";
+ }
+ }
+ ctx.fillStyle="blue";
+ ctx.fillText(debugText, 50, partIndex * 50 + 100);
+ }
+ if (ptLabels && selected) {
+ ctx.fillStyle="blue";
+ for (var typeIndex = 0; typeIndex < types.length; ++typeIndex) {
+ var type = types[typeIndex];
+ var index = pts[typeIndex];
+ for (var idx = pts[typeIndex]; idx < (type == 1 ? pts[typeIndex] + 6 : pts[typeIndex] + 4); idx += 2) {
+ drawPoint(scan[idx], scan[idx + 1]);
+ }
+ }
+ }
+ var infoText = "";
+ if (info_mode && selected) {
+ infoText += hasId >= 0 ? "id=" + scan[hasId] : partIndex;
+ }
+ if (intersect_mode && selected) {
+ if (scanType == ACTIVE_QUAD_SPAN) {
+ infoText += " t=" + scan[SPAN_Q_T];
+ } else if (scanType == ACTIVE_LINE_SPAN) {
+ infoText += " t=" + scan[SPAN_L_T];
+ } else if (scanType == INTERSECT_QUAD_LINE ||scanType == INTERSECT_QUAD) {
+ infoText += " t0[0]=" + scan[1] + " t1[0]=" + scan[10];
+ } else if (scanType == INTERSECT_QUAD_LINE_2 || scanType == INTERSECT_QUAD_2) {
+ infoText += " t0[0]=" + scan[1] + " t0[1]=" + scan[10] + " t1[0]=" + scan[13];
+ if (scanType == INTERSECT_QUAD_LINE_2) {
+ infoText += " t1[1]=" + scan[18];
+ } else {
+ infoText += " t0[1]=" + scan[20];
+ }
+ }
+ }
+ if (infoText.length > 0) {
+ ctx.fillStyle="green";
+ ctx.fillText(infoText, 10, (hasId >= 0 && sequence >= 0
+ ? hasId : partIndex) * 30 + 50);
+ }
+ if (intersect_mode && selected) {
+ ctx.fillStyle="rgba(50,100,200, 1.0)";
+ if (scanType == ACTIVE_QUAD_SPAN) {
+ drawPoint(scan[SPAN_Q_TX], scan[SPAN_Q_TY]);
+ } else if (scanType == ACTIVE_LINE_SPAN) {
+ drawPoint(scan[SPAN_L_TX], scan[SPAN_L_TY]);
+ } else if (scanType != INTERSECT_QUAD_NO && scanType != INTERSECT_QUAD_LINE_NO) {
+ drawPoint(scan[8], scan[9]);
+ if (scanType == INTERSECT_QUAD_LINE_2 || scanType == INTERSECT_QUAD_2) {
+ drawPoint(scan[11], scan[12]);
+ }
+ }
+ }
+ ctx.strokeStyle = "rgba(0,0,0, 0.2)";
+ scanType = -1;
+ }
+ drawDeferred();
+}
+
+function drawTop() {
+ init(tests[testIndex]);
+ redraw();
+}
+
+function redraw() {
+ ctx.beginPath();
+ ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height);
+ ctx.fillStyle="white";
+ ctx.fill();
+ draw(tests[testIndex], testTitles[testIndex]);
+}
+
+function doKeyPress(evt) {
+ var char = String.fromCharCode(evt.charCode);
+ switch (char) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (digit_mode) {
+ decimal_places = char - '0';
+ } else if (index_mode) {
+ index_bits ^= 1 << (char - '0');
+ }
+ redraw();
+ break;
+ case 'd':
+ digit_mode = true;
+ index_mode = false;
+ break;
+ case 'f':
+ info_mode ^= true;
+ redraw();
+ break;
+ case 'i':
+ digit_mode = false;
+ if (sequence >= 0) {
+ sequence = -1;
+ index_mode = false;
+ } else {
+ index_mode ^= true;
+ }
+ if (index_mode) {
+ index_bits = 0;
+ } else {
+ index_bits = -1;
+ }
+ redraw();
+ break;
+ case 'N':
+ testIndex += 9;
+ case 'n':
+ if (++testIndex >= tests.length)
+ testIndex = 0;
+ drawTop();
+ break;
+ case 'P':
+ testIndex -= 9;
+ case 'p':
+ if (--testIndex < 0)
+ testIndex = tests.length - 1;
+ drawTop();
+ break;
+ case 's':
+ sequence++;
+ redraw();
+ break;
+ case 'S':
+ sequence--;
+ if (sequence < 0) {
+ sequence = 0;
+ }
+ redraw();
+ break;
+ case 't':
+ intersect_mode ^= true;
+ redraw();
+ break;
+ case 'x':
+ ptLabels ^= true;
+ redraw();
+ break;
+ case 'y':
+ debug_xy ^= true;
+ redraw();
+ break;
+ case '-':
+ scale /= 2;
+ calcLeftTop();
+ redraw();
+ break;
+ case '=':
+ case '+':
+ scale *= 2;
+ calcLeftTop();
+ redraw();
+ break;
+ }
+}
+
+function calcXY() {
+ var e = window.event;
+ var tgt = e.target || e.srcElement;
+ var left = tgt.offsetLeft;
+ var top = tgt.offsetTop;
+ var unit = scale;
+ mouseX = (e.clientX - left) / scale + srcLeft;
+ mouseY = (e.clientY - top) / scale + srcTop;
+}
+
+function calcLeftTop() {
+ srcLeft = mouseX - screenWidth / 2 / scale;
+ srcTop = mouseY - screenHeight / 2 / scale;
+}
+
+function handleMouseClick() {
+ calcXY();
+ calcLeftTop();
+ redraw();
+}
+
+function handleMouseOver() {
+ calcXY();
+ var num = mouseX.toFixed(decimal_places) + ", " + mouseY.toFixed(decimal_places);
+ ctx.beginPath();
+ ctx.rect(300,100,200,10);
+ ctx.fillStyle="white";
+ ctx.fill();
+ ctx.fillStyle="black";
+ ctx.fillText(num, 300, 108);
+}
+
+function start() {
+ for (i = 0; i < testDivs.length; ++i) {
+ var title = testDivs[i].id.toString();
+ var str = testDivs[i].firstChild.data;
+ parse_debugShowActiveSpans(str, title);
+ parse_intersections(str, title);
+ }
+ drawTop();
+ window.addEventListener('keypress', doKeyPress, true);
+ window.onresize = function() {
+ drawTop();
+ }
+}
+
+</script>
+</head>
+
+<body onLoad="start();">
+<canvas id="canvas" width="750" height="500"
+ onmousemove="handleMouseOver()"
+ onclick="handleMouseClick()"
+ ></canvas >
+</body>
+</html>
diff --git a/experimental/Intersection/bc.htm b/experimental/Intersection/bc.htm
new file mode 100644
index 0000000000..fc9191a694
--- /dev/null
+++ b/experimental/Intersection/bc.htm
@@ -0,0 +1,455 @@
+<!-- bezier clip visualizer -->
+<html>
+<head>
+<div style="height:0">
+
+<div id="clip1">
+(gdb) p smaller
+$2 = {{
+ x = 0.91292418204644155,
+ y = 0.41931201426549197
+ }, {
+ x = 0.70491388044579517,
+ y = 0.64754305977710236
+ }, {
+ x = 0,
+ y = 1
+ }}
+(gdb) p larger
+$3 = {{
+ x = 0.21875,
+ y = 0.765625
+ }, {
+ x = 0.125,
+ y = 0.875
+ }, {
+ x = 0,
+ y = 1
+ }}
+(gdb) p distance2y
+$1 = {{
+ x = 0,
+ y = 0.080355482722450078
+ }, {
+ x = 0.5,
+ y = 0.038383741101172597
+ }, {
+ x = 1,
+ y = 0
+ }}
+</div>
+
+<div id="quad21a">
+bezier_clip q1=(0,0 1,0 0,2) q2=(0.5,0.25 0.5,0.5 0,1) minT=0 maxT=1
+</div>
+<div id="quad21b">
+bezier_clip q1=(0.5,0.25 0.5,0.375 0.375,0.5625) q2=(0,0 1,0 0,2) minT=0.3 maxT=0.78125
+</div>
+<div id="quad21c">
+bezier_clip q1=(0.42,0.18 0.6125,0.46875 0.341796875,1.22070312) q2=(0.5,0.25 0.5,0.375 0.375,0.5625) minT=0 maxT=0.926710098
+</div>
+<div id="quad21d">
+bezier_clip q1=(0.5,0.25 0.5,0.307919381 0.473162762,0.379257381) q2=(0.42,0.18 0.6125,0.46875 0.341796875,1.22070312) minT=0.187231244 maxT=0.729263299
+</div>
+<div id="quad21e">
+bezier_clip q1=(0.475846194,0.304363878 0.53317904,0.507883959 0.454423387,0.847492538) q2=(0.5,0.25 0.5,0.307919381 0.473162762,0.379257381) minT=0 maxT=1
+</div>
+<div id="quad21f">
+bezier_clip q1=(0.493290691,0.311274036 0.486581381,0.343588381 0.473162762,0.379257381) q2=(0.475846194,0.304363878 0.53317904,0.507883959 0.454423387,0.847492538) minT=0.0828748517 maxT=0.150086861
+</div>
+
+<div id="quad21g">
+(gdb) p smaller
+$1 = {{
+ x = 0.48441440743366754,
+ y = 0.33903196011243797
+ }, {
+ x = 0.48750982503868118,
+ y = 0.35346899178071778
+ }, {
+ x = 0.48999046908865357,
+ y = 0.368520797004039
+ }}
+(gdb) p larger
+$2 = {{
+ x = 0.49329069058425024,
+ y = 0.31127403581536672
+ }, {
+ x = 0.48658138116850047,
+ y = 0.34358838107698753
+ }, {
+ x = 0.47316276233700094,
+ y = 0.37925738104648321
+ }}
+</div>
+
+<div id="quad36">
+(gdb) p fQ
+$2 = {{
+ x = 1.8883839294261275,
+ y = 2.1108590606904345
+ }, {
+ x = 1.888463903363252,
+ y = 2.1111576060205435
+ }, {
+ x = 1.8885438199983176,
+ y = 2.1114561800016824
+ }}
+(gdb) p rh.fQ
+$3 = {{
+ x = 1.8883839294260976,
+ y = 2.1108590606903377
+ }, {
+ x = 1.8886366953645748,
+ y = 2.1109850143489544
+ }, {
+ x = 1.8888888888888888,
+ y = 2.1111111111111112
+ }}
+(gdb)
+</div>
+
+<div id="quad37">
+ {{x = 360.048828125, y = 229.2578125}, {x = 360.048828125, y = 224.4140625}, {x = 362.607421875, y = 221.3671875}}
+ {{x = 362.607421875, y = 221.3671875}, {x = 365.166015625, y = 218.3203125}, {x = 369.228515625, y = 218.3203125}}
+</div>
+
+<div id="quad38">
+$2 = {{fX = 369.969421, fY = 137.94809}, {fX = 383.982849, fY = 121.260353}, {fX = 406.233154, fY = 121.260353}}
+$4 = {{fX = 406.232788, fY = 121.260353}, {fX = 409.441956, fY = 121.260353}, {fX = 412.972046, fY = 121.795212}}
+</div>
+
+<div id="quad39">
+{{x = 406.233154296875, y = 121.26035308837891}, {x = 406.23153587045397, y = 121.26035308837891}, {x = 406.22991748761177, y = 121.26035317666889}},
+{{x = 406.23295158013377, y = 121.26035308872596}, {x = 406.2328698329315, y = 121.26035308837889}, {x = 406.2327880859375, y = 121.26035308837891}},
+</div>
+
+</div>
+
+<script type="text/javascript">
+
+var testDivs = [
+ quad56,
+ quad39,
+ quad38,
+ quad37,
+ quad36,
+ quad21g,
+ quad21a,
+ quad21b,
+ quad21c,
+ quad21d,
+ quad21e,
+ quad21f,
+ clip1,
+];
+
+var scale, columns, rows, xStart, yStart;
+
+var ticks = 10;
+var at_x = 13 + 0.5;
+var at_y = 13 + 0.5;
+var init_decimal_places = 1; // make this 3 to show more precision
+var decimal_places;
+var tests = [];
+var testTitles = [];
+var testIndex = 0;
+var ctx;
+var fat1 = true;
+var fat2 = false;
+var ctl1 = true;
+var ctl2 = false;
+var ctlPts1 = true;
+var ctlPts2 = false;
+var minScale = 1;
+var subscale = 1;
+
+function parse(test, title) {
+ var curveStrs = test.split("{{");
+ if (curveStrs.length == 1)
+ curveStrs = test.split("=(");
+ var pattern = /[a-z$=]?-?\d+\.*\d*/g;
+ var curves = [];
+ for (var c in curveStrs) {
+ var curveStr = curveStrs[c];
+ var points = curveStr.match(pattern);
+ var pts = [];
+ for (var wd in points) {
+ var num = parseFloat(points[wd]);
+ if (isNaN(num)) continue;
+ pts.push(num);
+ }
+ if (pts.length > 0)
+ curves.push(pts);
+ }
+ if (curves.length >= 2) {
+ tests.push(curves);
+ testTitles.push(title);
+ }
+}
+
+function init(test) {
+ var canvas = document.getElementById('canvas');
+ if (!canvas.getContext) return;
+ canvas.width = window.innerWidth - at_x;
+ canvas.height = window.innerHeight - at_y;
+ ctx = canvas.getContext('2d');
+ var xmin = Infinity;
+ var xmax = -Infinity;
+ var ymin = Infinity;
+ var ymax = -Infinity;
+ for (var curves in test) {
+ var curve = test[curves];
+ var last = curve.length;
+ for (var idx = 0; idx < last; idx += 2) {
+ xmin = Math.min(xmin, curve[idx]);
+ xmax = Math.max(xmax, curve[idx]);
+ ymin = Math.min(ymin, curve[idx + 1]);
+ ymax = Math.max(ymax, curve[idx + 1]);
+ }
+ }
+ subscale = 1;
+ decimal_places = init_decimal_places;
+ if (xmax != xmin && ymax != ymin) {
+ while ((xmax - xmin) * subscale < 0.1 && (ymax - ymin) * subscale < 0.1) {
+ subscale *= 10;
+ decimal_places += 1;
+ // if (subscale > 100000) {
+ // break;
+ // }
+ }
+ }
+ columns = Math.ceil(xmax * subscale) - Math.floor(xmin * subscale) + 1;
+ rows = Math.ceil(ymax * subscale) - Math.floor(ymin * subscale) + 1;
+
+ xStart = Math.floor(xmin * subscale) / subscale;
+ yStart = Math.floor(ymin * subscale) / subscale;
+ var hscale = ctx.canvas.width / columns / ticks;
+ var vscale = ctx.canvas.height / rows / ticks;
+ minScale = Math.floor(Math.min(hscale, vscale));
+ scale = minScale * subscale;
+ // while (columns < 1000 && rows < 1000) {
+ // columns *= 2;
+ // rows *= 2;
+ // }
+}
+
+function drawPoint(px, py, xoffset, yoffset, unit) {
+ var label = px.toFixed(decimal_places) + ", " + py.toFixed(decimal_places);
+ var _px = px * unit + xoffset;
+ var _py = py * unit + yoffset;
+ ctx.beginPath();
+ ctx.arc(_px, _py, 3, 0, Math.PI*2, true);
+ ctx.closePath();
+ ctx.fill();
+ ctx.fillText(label, _px + 5, _py);
+}
+
+function draw(test, title, _at_x, _at_y, scale) {
+ ctx.fillStyle = "rgba(0,0,0, 0.1)";
+ ctx.font = "normal 50px Arial";
+ ctx.fillText(title, 50, 50);
+ ctx.font = "normal 10px Arial";
+
+ var unit = scale * ticks;
+ ctx.lineWidth = 1;
+ var i;
+ for (i = 0; i <= rows * ticks; ++i) {
+ ctx.strokeStyle = (i % ticks) != 0 ? "rgb(160,160,160)" : "black";
+ ctx.beginPath();
+ ctx.moveTo(_at_x + 0, _at_y + i * minScale);
+ ctx.lineTo(_at_x + unit * columns, _at_y + i * minScale);
+ ctx.stroke();
+ }
+ for (i = 0; i <= columns * ticks; ++i) {
+ ctx.strokeStyle = (i % ticks) != 0 ? "rgb(160,160,160)" : "black";
+ ctx.beginPath();
+ ctx.moveTo(_at_x + i * minScale, _at_y + 0);
+ ctx.lineTo(_at_x + i * minScale, _at_y + unit * rows);
+ ctx.stroke();
+ }
+
+ var xoffset = xStart * -unit + _at_x;
+ var yoffset = yStart * -unit + _at_y;
+
+ ctx.fillStyle = "rgb(40,80,60)"
+ for (i = 0; i <= columns; i += (1 / ticks))
+ {
+ num = xStart + i / subscale;
+ ctx.fillText(num.toFixed(decimal_places), xoffset + num * unit - 5, 10);
+ }
+ for (i = 0; i <= rows; i += (1 / ticks))
+ {
+ num = yStart + i / subscale;
+ ctx.fillText(num.toFixed(decimal_places), 0, yoffset + num * unit + 0);
+ }
+
+ // draw curve 1 and 2
+ var curves, pts;
+ for (curves in test) {
+ var curve = test[curves];
+ ctx.beginPath();
+ ctx.moveTo(xoffset + curve[0] * unit, yoffset + curve[1] * unit);
+ switch (curve.length) {
+ case 6:
+ ctx.quadraticCurveTo(
+ xoffset + curve[2] * unit, yoffset + curve[3] * unit,
+ xoffset + curve[4] * unit, yoffset + curve[5] * unit);
+ break;
+ case 8:
+ ctx.bezierCurveTo(
+ xoffset + curve[2] * unit, yoffset + curve[3] * unit,
+ xoffset + curve[4] * unit, yoffset + curve[5] * unit,
+ xoffset + curve[6] * unit, yoffset + curve[7] * unit);
+ break;
+ }
+ if (curves == 2) ctx.strokeStyle = curves ? "red" : "blue";
+ ctx.stroke();
+ ctx.strokeStyle = "rgba(0,0,0, 0.3)";
+ ctx.beginPath();
+ ctx.moveTo(xoffset + curve[0] * unit, yoffset + curve[1] * unit);
+ ctx.lineTo(xoffset + curve[2] * unit, yoffset + curve[3] * unit);
+ ctx.lineTo(xoffset + curve[4] * unit, yoffset + curve[5] * unit);
+ if (curve.length == 8)
+ ctx.lineTo(xoffset + curve[6] * unit, yoffset + curve[7] * unit);
+ ctx.stroke();
+ }
+ // optionally draw fat lines for curve
+ if (fat1)
+ drawFat(test[0], xoffset, yoffset, unit);
+ if (fat2)
+ drawFat(test[1], xoffset, yoffset, unit);
+ if (ctl1)
+ drawCtl(test[0], xoffset, yoffset, unit);
+ if (ctl2)
+ drawCtl(test[1], xoffset, yoffset, unit);
+ if (ctlPts1)
+ drawCtlPts(test[0], xoffset, yoffset, unit);
+ if (ctlPts2)
+ drawCtlPts(test[1], xoffset, yoffset, unit);
+}
+
+function drawCtl(curve, xoffset, yoffset, unit) {
+ var last = curve.length - 2;
+ ctx.strokeStyle = "rgba(0,0,0, 0.5)";
+ ctx.beginPath();
+ ctx.moveTo(xoffset + curve[0] * unit, yoffset + curve[1] * unit);
+ ctx.lineTo(xoffset + curve[2] * unit, yoffset + curve[3] * unit);
+ ctx.lineTo(xoffset + curve[4] * unit, yoffset + curve[5] * unit);
+ ctx.stroke();
+}
+
+function drawCtlPts(curve, xoffset, yoffset, unit) {
+ drawPoint(curve[0], curve[1], xoffset, yoffset, unit);
+ drawPoint(curve[2], curve[3], xoffset, yoffset, unit);
+ drawPoint(curve[4], curve[5], xoffset, yoffset, unit);
+}
+
+function drawFat(curve, xoffset, yoffset, unit) {
+ var last = curve.length - 2;
+ ctx.strokeStyle = "rgba(0,0,0, 0.5)";
+ ctx.beginPath();
+ ctx.moveTo(xoffset + curve[0] * unit, yoffset + curve[1] * unit);
+ ctx.lineTo(xoffset + curve[last] * unit, yoffset + curve[last + 1] * unit);
+ ctx.stroke();
+ // draw line parallel to end points through control points
+ var dx = curve[last] - curve[0];
+ var dy = curve[last + 1] - curve[1];
+ drawParallelLine(curve[2], curve[3], dx, dy, xoffset, yoffset, unit);
+ if (curve.length == 8)
+ drawParallelLine(curve[4], curve[5], dx, dy, xoffset, yoffset, unit);
+}
+
+function drawParallelLine(x, y, dx, dy, xoffset, yoffset, unit) {
+ var x1 = x - dx;
+ var y1 = y - dy;
+ var x2 = x + dx;
+ var y2 = y + dy;
+ ctx.beginPath();
+ ctx.moveTo(xoffset + x1 * unit, yoffset + y1 * unit);
+ ctx.lineTo(xoffset + x2 * unit, yoffset + y2 * unit);
+ ctx.stroke();
+}
+
+function drawTop() {
+ init(tests[testIndex]);
+ redraw();
+}
+
+function redraw() {
+ ctx.beginPath();
+ ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height);
+ ctx.fillStyle="white";
+ ctx.fill();
+ draw(tests[testIndex], testTitles[testIndex], at_x, at_y, scale);
+}
+
+function doKeyPress(evt) {
+ var char = String.fromCharCode(evt.charCode);
+ switch (char) {
+ case 'c':
+ ctl2 ^= true;
+ if (ctl2 == false)
+ ctl1 ^= true;
+ drawTop();
+ break;
+ case 'd':
+ ctlPts2 ^= true;
+ if (ctlPts2 == false)
+ ctlPts1 ^= true;
+ drawTop();
+ break;
+ case 'f':
+ fat2 ^= true;
+ if (fat2 == false)
+ fat1 ^= true;
+ drawTop();
+ break;
+ case 'N':
+ testIndex += 9;
+ case 'n':
+ if (++testIndex >= tests.length)
+ testIndex = 0;
+ mouseX = Infinity;
+ drawTop();
+ break;
+ case 'P':
+ testIndex -= 9;
+ case 'p':
+ if (--testIndex < 0)
+ testIndex = tests.length - 1;
+ mouseX = Infinity;
+ drawTop();
+ break;
+ }
+}
+
+function handleMouseClick() {
+}
+
+function handleMouseOver() {
+}
+
+function start() {
+ for (i = 0; i < testDivs.length; ++i) {
+ var title = testDivs[i].id.toString();
+ var str = testDivs[i].firstChild.data;
+ parse(str, title);
+ }
+ drawTop();
+ window.addEventListener('keypress', doKeyPress, true);
+ window.onresize = function() {
+ drawTop();
+ }
+}
+
+</script>
+</head>
+
+<body onLoad="start();">
+<canvas id="canvas" width="750" height="500"
+ onmousemove="handleMouseOver()"
+ onclick="handleMouseClick()"
+ ></canvas >
+</body>
+</html>
diff --git a/experimental/Intersection/op.htm b/experimental/Intersection/op.htm
new file mode 100644
index 0000000000..e251a8e7f2
--- /dev/null
+++ b/experimental/Intersection/op.htm
@@ -0,0 +1,3573 @@
+<!-- path visualizer -->
+<html>
+<head>
+<div style="height:0">
+
+<div id="testSimplifyQuadratic1">
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 1, 1);
+ path.close();
+ path.moveTo(1, 0);
+ path.quadTo(0, 0, 0, 1);
+ path.close();
+ testSimplify(path, true, out, bitmap);
+}
+</div>
+
+<div id="testSimplifyQuadratic2">
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.quadTo(20, 0, 20, 20);
+ path.close();
+ path.moveTo(20, 0);
+ path.quadTo(0, 0, 0, 20);
+ path.close();
+ testSimplify(path, true, out, bitmap);
+}
+</div>
+
+<div id="testSimplifyQuadratic3">
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.quadTo(20, 0, 20, 20);
+ path.close();
+ path.moveTo(0, 20);
+ path.quadTo(0, 0, 20, 0);
+ path.close();
+ testSimplify(path, true, out, bitmap);
+}
+</div>
+
+<div id="testSimplifyQuadratic4">
+ SkPath path, out;
+ path.moveTo(0, 20);
+ path.quadTo(20, 0, 40, 20);
+ path.close();
+ path.moveTo(40, 10);
+ path.quadTo(20, 30, 0, 10);
+ path.close();
+ testSimplify(path, true, out, bitmap);
+ drawAsciiPaths(path, out, true);
+}
+</div>
+
+<div id="testSimplifyQuadratic5">
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.lineTo(0, 0);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(0, 0, 0, 1);
+ path.close();
+ testSimplify(path, true, out, bitmap);
+ drawAsciiPaths(path, out, true);
+}
+</div>
+
+<div id="testSimplifyQuadratic6">
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.lineTo(1, 0);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.close();
+ testSimplify(path, true, out, bitmap);
+ drawAsciiPaths(path, out, true);
+}
+</div>
+
+<div id="testSimplifyQuadratic7">
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.lineTo(0, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(1, 0, 0, 2);
+ path.close();
+ testSimplify(path, true, out, bitmap);
+ drawAsciiPaths(path, out, true);
+}
+</div>
+
+<div id="testSimplifyQuadratic8">
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.lineTo(0, 0);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(1, 0, 0, 2);
+ path.close();
+ testSimplify(path, true, out, bitmap);
+ drawAsciiPaths(path, out, true);
+}
+</div>
+
+<div id="testSimplifyQuadratic9">
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.lineTo(1, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(1, 0, 2, 2);
+ path.close();
+ testSimplify(path, true, out, bitmap);
+ drawAsciiPaths(path, out, true);
+}
+</div>
+
+<div id="testSimplifyQuadratic10">
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.lineTo(0, 0);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 1);
+ path.quadTo(1, 1, 1, 2);
+ path.close();
+ testSimplify(path, true, out, bitmap);
+ drawAsciiPaths(path, out, true);
+}
+</div>
+
+<div id="testSimplifyQuadratic11">
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.lineTo(0, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(2, 1);
+ path.quadTo(2, 2, 3, 3);
+ path.close();
+ testSimplify(path, true, out, bitmap);
+ drawAsciiPaths(path, out, true);
+}
+</div>
+
+<div id="testSimplifyQuadratic12">
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.lineTo(0, 2);
+ path.lineTo(0, 0);
+ path.close();
+ path.moveTo(3, 0);
+ path.quadTo(1, 1, 0, 2);
+ path.lineTo(3, 0);
+ path.close();
+ testSimplify(path, true, out, bitmap);
+ drawAsciiPaths(path, out, true);
+}
+</div>
+
+<div id="testSimplifyQuadratic13">
+ SkPath path, out;
+path.moveTo(0, 0);
+path.quadTo(0, 0, 1, 0);
+path.lineTo(1, 1);
+path.lineTo(0, 0);
+path.close();
+path.moveTo(0, 0);
+path.quadTo(3, 0, 1, 1);
+path.lineTo(0, 0);
+path.close();
+ testSimplify(path, true, out, bitmap);
+ drawAsciiPaths(path, out, true);
+}
+</div>
+
+<div id="testSimplifyQuadratic14">
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.lineTo(1, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(0, 1, 2, 1);
+ path.close();
+ testSimplify(path, true, out, bitmap);
+ drawAsciiPaths(path, out, true);
+}
+</div>
+
+<div id="testSimplifyQuadratic15">
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 1, 3);
+ path.lineTo(3, 3);
+ path.close();
+ path.moveTo(0, 1);
+ path.lineTo(1, 1);
+ path.quadTo(0, 3, 3, 3);
+ path.close();
+ testSimplify(path, true, out, bitmap);
+ drawAsciiPaths(path, out, true);
+}
+</div>
+
+<div id="testSimplifyQuadratic16">
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.lineTo(0, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.close();
+ testSimplify(path, true, out, bitmap);
+ drawAsciiPaths(path, out, true);
+}
+</div>
+
+<div id="testSimplifyQuadratic17">
+ SkPath path, out;
+ path.moveTo(8, 8);
+ path.quadTo(10, 10, 8, -10);
+ path.close();
+ path.moveTo(8, 8);
+ path.quadTo(12, 12, 14, 4);
+ path.close();
+ path.moveTo(8, 8);
+ path.quadTo(9, 9, 10, 8);
+ path.close();
+ testSimplify(path, true, out, bitmap);
+ drawAsciiPaths(path, out, true);
+}
+</div>
+
+<div id="testSimplifyQuadratic18">
+ SkPath path, out;
+ path.moveTo(8.0000000000000071, 8.0000000000000071);
+ path.quadTo(8.7289570079366854, 8.7289570079366889, 9.3914917259458743, 9.0593802763083691);
+ path.close();
+ path.moveTo(8.0000000000000142, 8.0000000000000142);
+ path.quadTo(8.1250000000000107, 8.1250000000000071, 8.2500000000000071, 8.2187500000000053);
+ path.close();
+ testSimplify(path, true, out, bitmap);
+ drawAsciiPaths(path, out, true);
+</div>
+
+<div id="testSimplifyQuadratic19">
+ SkPath path, simple;
+ path.moveTo(0,4);
+ path.lineTo(6,4);
+ path.lineTo(3,1);
+ path.close();
+ path.moveTo(2,3);
+ path.lineTo(3,2);
+ path.lineTo(4,3);
+ path.close();
+ testSimplifyx(path);
+</div>
+
+<div id="testSimplifyQuadratic20">
+ SkPath path, simple;
+ path.moveTo(0,4);
+ path.lineTo(6,4);
+ path.lineTo(3,1);
+ path.close();
+ path.moveTo(2,3);
+ path.lineTo(4,3);
+ path.lineTo(3,2);
+ path.close();
+ testSimplifyx(path);
+</div>
+
+<div id="testSimplifyQuadratic21">
+ SkPath path, simple;
+ path.moveTo(0,4);
+ path.lineTo(8,4);
+ path.lineTo(4,0);
+ path.close();
+ path.moveTo(2,2);
+ path.lineTo(3,3);
+ path.lineTo(4,2);
+ path.close();
+ testSimplifyx(path);
+</div>
+
+<div id="testLine6">
+ SkPath path, simple;
+ path.moveTo(0,0);
+ path.lineTo(4,0);
+ path.lineTo(2,2);
+ path.close();
+ path.moveTo(2,0);
+ path.lineTo(6,0);
+ path.lineTo(4,2);
+ path.close();
+ testSimplifyx(path);
+</div>
+
+<div id="testLine7">
+ SkPath path, simple;
+ path.moveTo(0,0);
+ path.lineTo(4,0);
+ path.lineTo(2,2);
+ path.close();
+ path.moveTo(6,0);
+ path.lineTo(2,0);
+ path.lineTo(4,2);
+ path.close();
+ testSimplifyx(path);
+</div>
+
+<div id="testLine7b">
+ path.moveTo(0,0);
+ path.lineTo(4,0);
+ path.close();
+ path.moveTo(6,0);
+ path.lineTo(2,0);
+ path.lineTo(4,2);
+ path.close();
+</div>
+
+<div id="testLine9">
+ SkPath path, simple;
+ path.moveTo(0,4);
+ path.lineTo(4,4);
+ path.lineTo(2,2);
+ path.close();
+ path.moveTo(6,4);
+ path.lineTo(2,4);
+ path.lineTo(4,2);
+ path.close();
+ testSimplifyx(path);
+</div>
+
+<div id="testLine12">
+ path.moveTo(0,4);
+ path.lineTo(6,4);
+ path.lineTo(3,1);
+ path.close();
+ path.moveTo(2,3);
+ path.lineTo(3,2);
+ path.lineTo(4,3);
+ path.close();
+</div>
+
+<div id="testLine13">
+ path.moveTo(6,4);
+ path.lineTo(0,4);
+ path.lineTo(3,1);
+ path.close();
+ path.moveTo(3,2);
+ path.lineTo(2,3);
+ path.lineTo(4,3);
+ path.close();
+</div>
+
+<div id="testLine17">
+ SkPath path, simple;
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 12, 13, 13, SkPath::kCW_Direction);
+ testSimplifyx(path);
+</div>
+
+<div id="testLine19">
+ SkPath path, simple;
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(12, 16, 21, 21, SkPath::kCW_Direction);
+ testSimplifyx(path);
+</div>
+
+<div id="testLine22">
+ path.addRect(0, 12, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 12, 13, 13, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine24">
+ path.addRect(0, 18, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 12, 13, 13, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine28">
+ SkPath path, simple;
+ path.addRect(0, 6, 12, 12, SkPath::kCW_Direction);
+ path.addRect(0, 0, 9, 9, SkPath::kCW_Direction);
+ testSimplifyx(path);
+</div>
+
+<div id="testLine29">
+ SkPath path, simple;
+ path.addRect(0, 18, 12, 12, SkPath::kCW_Direction);
+ path.addRect(12, 12, 21, 21, SkPath::kCW_Direction);
+ testSimplifyx(path);
+</div>
+
+<div id="testLine30">
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 4, 13, 13, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine31">
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(0, 4, 9, 9, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine32">
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 12, 13, 13, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine33">
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 16, 13, 13, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine34">
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 6, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 12, 13, 13, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine35">
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(6, 0, 18, 18, SkPath::kCW_Direction);
+ path.addRect(4, 16, 13, 13, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine36">
+ path.addRect(0, 10, 20, 20, SkPath::kCW_Direction);
+ path.addRect(6, 12, 18, 18, SkPath::kCW_Direction);
+ path.addRect(4, 16, 13, 13, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine37">
+ path.addRect(0, 20, 20, 20, SkPath::kCW_Direction);
+ path.addRect(18, 24, 30, 30, SkPath::kCW_Direction);
+ path.addRect(0, 0, 9, 9, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine38">
+ path.addRect(10, 0, 30, 30, SkPath::kCW_Direction);
+ path.addRect(6, 12, 18, 18, SkPath::kCW_Direction);
+ path.addRect(12, 12, 21, 21, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine39">
+ path.addRect(10, 0, 30, 30, SkPath::kCW_Direction);
+ path.addRect(12, 6, 24, 24, SkPath::kCW_Direction);
+ path.addRect(12, 4, 21, 21, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine40">
+ path.addRect(10, 0, 30, 30, SkPath::kCW_Direction);
+ path.addRect(12, 18, 24, 24, SkPath::kCW_Direction);
+ path.addRect(4, 16, 13, 13, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine41">
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(18, 24, 30, 30, SkPath::kCW_Direction);
+ path.addRect(12, 0, 21, 21, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine42">
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(8, 16, 17, 17, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine43">
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(6, 24, 18, 18, SkPath::kCW_Direction);
+ path.addRect(0, 32, 9, 36, SkPath::kCCW_Direction);
+</div>
+
+<div id="testLine44">
+ path.addRect(10, 40, 30, 30, SkPath::kCW_Direction);
+ path.addRect(18, 0, 30, 30, SkPath::kCW_Direction);
+ path.addRect(18, 32, 27, 36, SkPath::kCCW_Direction);
+</div>
+
+<div id="testLine45">
+ path.addRect(10, 0, 30, 30, SkPath::kCW_Direction);
+ path.addRect(18, 0, 30, 30, SkPath::kCW_Direction);
+ path.addRect(24, 32, 33, 36, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine46">
+ path.addRect(10, 40, 30, 30, SkPath::kCW_Direction);
+ path.addRect(24, 0, 36, 36, SkPath::kCW_Direction);
+ path.addRect(24, 32, 33, 36, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine47">
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(0, 0, 9, 9, SkPath::kCCW_Direction);
+</div>
+
+<div id="testLine48">
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 6, 12, 12, SkPath::kCW_Direction);
+ path.addRect(0, 0, 9, 9, SkPath::kCCW_Direction);
+</div>
+
+<div id="testLine49">
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(0, 0, 9, 9, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine50">
+ path.addRect(10, 30, 30, 30, SkPath::kCW_Direction);
+ path.addRect(24, 20, 36, 30, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine51">
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 12, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 12, 13, 13, SkPath::kCCW_Direction);
+</div>
+
+<div id="testLine52">
+ path.addRect(0, 30, 20, 20, SkPath::kCW_Direction);
+ path.addRect(6, 20, 18, 30, SkPath::kCW_Direction);
+ path.addRect(32, 0, 36, 41, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine53">
+ path.addRect(10, 30, 30, 30, SkPath::kCW_Direction);
+ path.addRect(12, 20, 24, 30, SkPath::kCW_Direction);
+ path.addRect(12, 32, 21, 36, SkPath::kCCW_Direction);
+</div>
+
+<div id="testLine54">
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(6, 0, 18, 18, SkPath::kCW_Direction);
+ path.addRect(8, 4, 17, 17, SkPath::kCCW_Direction);
+</div>
+
+<div id="testLine55">
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(6, 6, 18, 18, SkPath::kCW_Direction);
+ path.addRect(4, 4, 13, 13, SkPath::kCCW_Direction);
+</div>
+
+<div id="testLine56">
+ path.addRect(0, 20, 20, 20, SkPath::kCW_Direction);
+ path.addRect(18, 20, 30, 30, SkPath::kCW_Direction);
+ path.addRect(12, 0, 21, 21, SkPath::kCCW_Direction);
+</div>
+
+<div id="testLine57">
+ path.addRect(20, 0, 40, 40, SkPath::kCW_Direction);
+ path.addRect(20, 0, 30, 40, SkPath::kCW_Direction);
+ path.addRect(12, 0, 21, 21, SkPath::kCCW_Direction);
+</div>
+
+<div id="testLine58">
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 0, 12, 12, SkPath::kCCW_Direction);
+ path.addRect(0, 12, 9, 9, SkPath::kCCW_Direction);
+</div>
+
+<div id="testLine59">
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(6, 6, 18, 18, SkPath::kCCW_Direction);
+ path.addRect(4, 4, 13, 13, SkPath::kCCW_Direction);
+</div>
+
+<div id="testLine60">
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(6, 12, 18, 18, SkPath::kCCW_Direction);
+ path.addRect(4, 12, 13, 13, SkPath::kCCW_Direction);
+</div>
+
+<div id="testLine61">
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(12, 0, 24, 24, SkPath::kCCW_Direction);
+ path.addRect(12, 0, 21, 21, SkPath::kCCW_Direction);
+</div>
+
+<div id="testLine62">
+ path.addRect(0, 0, 60, 60, SkPath::kCW_Direction);
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 12, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 12, 13, 13, SkPath::kCCW_Direction);
+</div>
+
+<div id="testLine63">
+ path.addRect(0, 0, 60, 60, SkPath::kCW_Direction);
+ path.addRect(0, 10, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 6, 12, 12, SkPath::kCCW_Direction);
+ path.addRect(0, 32, 9, 36, SkPath::kCCW_Direction);
+</div>
+
+<div id="testLine64">
+ path.addRect(0, 0, 60, 60, SkPath::kCW_Direction);
+ path.addRect(10, 40, 30, 30, SkPath::kCW_Direction);
+ path.addRect(18, 6, 30, 30, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine65">
+ path.addRect(0, 0, 60, 60, SkPath::kCW_Direction);
+ path.addRect(10, 0, 30, 30, SkPath::kCW_Direction);
+ path.addRect(24, 0, 36, 36, SkPath::kCW_Direction);
+ path.addRect(32, 6, 36, 41, SkPath::kCCW_Direction);
+</div>
+
+<div id="testLine66">
+ path.addRect(0, 0, 60, 60, SkPath::kCW_Direction);
+ path.addRect(0, 30, 20, 20, SkPath::kCW_Direction);
+ path.addRect(12, 20, 24, 30, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine67">
+ path.addRect(0, 0, 60, 60, SkPath::kCW_Direction);
+ path.addRect(10, 40, 30, 30, SkPath::kCW_Direction);
+ path.addRect(24, 20, 36, 30, SkPath::kCW_Direction);
+ path.addRect(32, 0, 36, 41, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine68a">
+ path.addRect(0, 0, 8, 8, SkPath::kCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCW_Direction);
+ path.addRect(1, 2, 4, 2, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine68b">
+ path.addRect(0, 0, 8, 8, SkPath::kCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCCW_Direction);
+ path.addRect(1, 2, 4, 2, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine68c">
+ path.addRect(0, 0, 8, 8, SkPath::kCCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCW_Direction);
+ path.addRect(1, 2, 4, 2, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine68d">
+ path.addRect(0, 0, 8, 8, SkPath::kCCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCCW_Direction);
+ path.addRect(1, 2, 4, 2, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine68e">
+ path.addRect(0, 0, 8, 8, SkPath::kCW_Direction);
+ path.addRect(0, 0, 8, 8, SkPath::kCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCCW_Direction);
+ path.addRect(1, 2, 2, 2, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine68f">
+ path.addRect(0, 0, 8, 8, SkPath::kCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCCW_Direction);
+ path.addRect(2, 2, 6, 6, SkPath::kCCW_Direction);
+ path.addRect(1, 2, 2, 2, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine69">
+ path.addRect(0, 20, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 20, 12, 30, SkPath::kCW_Direction);
+ path.addRect(12, 32, 21, 36, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine70">
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 24, 12, 12, SkPath::kCW_Direction);
+ path.addRect(12, 32, 21, 36, SkPath::kCCW_Direction);
+</div>
+
+<div id="testLine71">
+ path.addRect(0, 0, 20, 20, SkPath::kCW_Direction);
+ path.addRect(12, 0, 24, 24, SkPath::kCW_Direction);
+ path.addRect(12, 32, 21, 36, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine72">
+ path.addRect(0, 0, 60, 60, SkPath::kCW_Direction);
+ path.addRect(10, 40, 30, 30, SkPath::kCW_Direction);
+ path.addRect(6, 20, 18, 30, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine73">
+ path.addRect(0, 0, 60, 60, SkPath::kCW_Direction);
+ path.addRect(0, 40, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 20, 12, 30, SkPath::kCW_Direction);
+ path.addRect(0, 0, 9, 9, SkPath::kCCW_Direction);
+</div>
+
+<div id="testLine74">
+ path.addRect(20, 30, 40, 40, SkPath::kCW_Direction);
+ path.addRect(24, 20, 36, 30, SkPath::kCCW_Direction);
+ path.addRect(32, 24, 36, 41, SkPath::kCCW_Direction);
+</div>
+
+<div id="testLine75">
+ path.addRect(0, 0, 60, 60, SkPath::kCW_Direction);
+ path.addRect(10, 0, 30, 30, SkPath::kCCW_Direction);
+ path.addRect(18, 0, 30, 30, SkPath::kCCW_Direction);
+ path.addRect(12, 0, 21, 21, SkPath::kCCW_Direction);
+</div>
+
+<div id="testLine76">
+ path.addRect(36, 0, 66, 60, SkPath::kCW_Direction);
+ path.addRect(10, 20, 40, 30, SkPath::kCW_Direction);
+ path.addRect(24, 20, 36, 30, SkPath::kCCW_Direction);
+ path.addRect(32, 6, 36, 41, SkPath::kCCW_Direction);
+</div>
+
+<div id="testLine77">
+ path.addRect(20, 0, 40, 40, SkPath::kCW_Direction);
+ path.addRect(24, 6, 36, 36, SkPath::kCCW_Direction);
+ path.addRect(24, 32, 33, 36, SkPath::kCCW_Direction);
+</div>
+
+<div id="testLine78">
+ path.addRect(0, 0, 30, 60, SkPath::kCW_Direction);
+ path.addRect(10, 20, 30, 30, SkPath::kCCW_Direction);
+ path.addRect(18, 20, 30, 30, SkPath::kCCW_Direction);
+ path.addRect(32, 0, 36, 41, SkPath::kCCW_Direction);
+</div>
+
+<div id="testLine79">
+ path.addRect(0, 36, 60, 30, SkPath::kCW_Direction);
+ path.addRect(10, 30, 40, 30, SkPath::kCW_Direction);
+ path.addRect(0, 20, 12, 30, SkPath::kCCW_Direction);
+ path.addRect(0, 32, 9, 36, SkPath::kCCW_Direction);
+</div>
+
+<div id="testLine81">
+ path.addRect(-1, -1, 3, 3, SkPath::kCW_Direction);
+ path.addRect(0, 0, 1, 1, SkPath::kCW_Direction);
+ path.addRect(1, 1, 2, 2, SkPath::kCCW_Direction);
+</div>
+
+<div id="testDegenerate1">
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.lineTo(2, 0);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(2, 0);
+ path.close();
+</div>
+
+<div id="testDegenerate2">
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.lineTo(0, 0);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(0, 1);
+ path.close();
+</div>
+
+<div id="testDegenerate3">
+ path.moveTo(0, 0);
+ path.lineTo(2, 0);
+ path.lineTo(1, 0);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.lineTo(3, 0);
+ path.close();
+</div>
+
+<div id="testDegenerate4">
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(1, 3);
+ path.close();
+ path.moveTo(1, 0);
+ path.lineTo(1, 1);
+ path.lineTo(1, 2);
+ path.close();
+</div>
+
+<div id="testNondegenerate1">
+ path.moveTo(0, 0);
+ path.lineTo(3, 0);
+ path.lineTo(1, 3);
+ path.close();
+ path.moveTo(1, 1);
+ path.lineTo(2, 1);
+ path.lineTo(1, 2);
+ path.close();
+</div>
+
+<div id="testNondegenerate2">
+ path.moveTo(1, 0);
+ path.lineTo(0, 1);
+ path.lineTo(1, 1);
+ path.close();
+ path.moveTo(0, 2);
+ path.lineTo(0, 3);
+ path.lineTo(1, 2);
+ path.close();
+</div>
+
+<div id="testNondegenerate3">
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(2, 1);
+ path.close();
+ path.moveTo(0, 1);
+ path.lineTo(1, 1);
+ path.lineTo(0, 2);
+ path.close();
+</div>
+
+<div id="testNondegenerate4">
+ path.moveTo(1, 0);
+ path.lineTo(0, 1);
+ path.lineTo(1, 2);
+ path.close();
+ path.moveTo(0, 2);
+ path.lineTo(0, 3);
+ path.lineTo(1, 3);
+ path.close();
+</div>
+
+<div id="testQuadralateral5">
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(1, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(2, 2);
+ path.lineTo(3, 2);
+ path.lineTo(3, 3);
+ path.close();
+</div>
+
+<div id="testQuadralateral6">
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(1, 1);
+ path.close();
+ path.moveTo(1, 0);
+ path.lineTo(2, 0);
+ path.lineTo(0, 2);
+ path.lineTo(2, 2);
+ path.close();
+</div>
+
+<div id="testFauxQuadralateral6">
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(1, 1);
+ path.close();
+ path.moveTo(1, 0);
+ path.lineTo(2, 0);
+ path.lineTo(1.333, 0.667);
+ path.close();
+ path.moveTo(1.333, 0.667);
+ path.lineTo(0, 2);
+ path.lineTo(2, 2);
+ path.close();
+</div>
+
+<div id="testFauxQuadralateral6a">
+ path.moveTo(0, 0);
+ path.lineTo(3, 0);
+ path.lineTo(3, 3);
+ path.close();
+ path.moveTo(3, 0);
+ path.lineTo(6, 0);
+ path.lineTo(4, 2);
+ path.close();
+ path.moveTo(4, 2);
+ path.lineTo(0, 6);
+ path.lineTo(6, 6);
+ path.close();
+</div>
+
+<div id="testFauxQuadralateral6b">
+ path.moveTo(0, 0);
+ path.lineTo(3, 0);
+ path.lineTo(3, 3);
+ path.close();
+ path.moveTo(3, 0);
+ path.lineTo(6, 0);
+ path.lineTo(4, 2);
+ path.close();
+ path.moveTo(4, 2);
+ path.lineTo(6, 6);
+ path.lineTo(0, 6);
+ path.close();
+</div>
+
+<div id="testFauxQuadralateral6c">
+ path.moveTo(0, 0);
+ path.lineTo(3, 3);
+ path.lineTo(3, 0);
+ path.close();
+ path.moveTo(3, 0);
+ path.lineTo(6, 0);
+ path.lineTo(4, 2);
+ path.close();
+ path.moveTo(4, 2);
+ path.lineTo(0, 6);
+ path.lineTo(6, 6);
+ path.close();
+</div>
+
+<div id="testFauxQuadralateral6d">
+ path.moveTo(0, 0);
+ path.lineTo(3, 3);
+ path.lineTo(3, 0);
+ path.close();
+ path.moveTo(3, 0);
+ path.lineTo(6, 0);
+ path.lineTo(4, 2);
+ path.close();
+ path.moveTo(4, 2);
+ path.lineTo(6, 6);
+ path.lineTo(0, 6);
+</div>
+
+<div id="testQuadralateral6a">
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.lineTo(3, 0);
+ path.lineTo(3, 3);
+ path.close();
+ path.moveTo(3, 0);
+ path.lineTo(6, 0);
+ path.lineTo(0, 6);
+ path.lineTo(6, 6);
+</div>
+
+<div id="testQuadralateral7">
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(2, 1);
+ path.close();
+ path.moveTo(1, 0);
+ path.lineTo(1, 1);
+ path.lineTo(2, 2);
+ path.lineTo(1, 3);
+ path.close();
+</div>
+
+<div id="testQuadralateral8">
+ path.moveTo(0, 0);
+ path.lineTo(3, 1);
+ path.lineTo(1, 3);
+ path.lineTo(3, 3);
+ path.close();
+ path.moveTo(2, 1);
+ path.lineTo(0, 2);
+ path.lineTo(3, 2);
+ path.lineTo(2, 3);
+ path.close();
+</div>
+
+<div id="testQuadralateral9">
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(1, 2);
+ path.lineTo(2, 2);
+ path.close();
+ path.moveTo(1, 1);
+ path.lineTo(2, 1);
+ path.lineTo(1, 3);
+ path.lineTo(2, 3);
+ path.close();
+</div>
+
+<div id="testLine1x">
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 0, 13, 13, SkPath::kCW_Direction);
+</div>
+
+<div id="testLine2x">
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(0, 20, 20, 20, SkPath::kCW_Direction);
+ path.addRect(0, 20, 12, 30, SkPath::kCW_Direction);
+ path.addRect(12, 0, 21, 21, SkPath::kCCW_Direction);
+</div>
+
+<div id="testLine3x">
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(10, 30, 30, 30, SkPath::kCW_Direction);
+ path.addRect(18, 20, 30, 30, SkPath::kCCW_Direction);
+ path.addRect(0, 32, 9, 36, SkPath::kCCW_Direction);
+</div>
+
+<div id="testLine4x">
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addRect(10, 30, 30, 30, SkPath::kCW_Direction);
+ path.addRect(24, 20, 36, 30, SkPath::kCCW_Direction);
+ path.addRect(0, 32, 9, 36, SkPath::kCCW_Direction);
+</div>
+
+<div id="testQuadratic1">
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.lineTo(1, 0);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.close();
+</div>
+
+<div id="testQuadratic2">
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.lineTo(3, 0);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.close();
+</div>
+
+<div id="testQuadratic3">
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 1, 0);
+ path.lineTo(0, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.close();
+</div>
+
+<div id="testQuadratic4x">
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 1, 0);
+ path.lineTo(0, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(1, 0, 0, 2);
+ path.close();
+</div>
+
+<div id="testQuadratic5">
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.lineTo(0, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.quadTo(0, 1, 0, 2);
+ path.close();
+</div>
+
+<div id="testQuadratic6">
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 1, 0);
+ path.lineTo(2, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(2, 0, 0, 1);
+ path.close();
+</div>
+
+<div id="testQuadratic7">
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 1, 0);
+ path.lineTo(3, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(3, 0, 1, 2);
+ path.close();
+</div>
+
+<div id="testQuadratic8">
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 1, 0);
+ path.lineTo(0, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.quadTo(0, 1, 1, 2);
+ path.close();
+</div>
+
+<div id="testQuadratic9">
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 1, 0);
+ path.lineTo(3, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.quadTo(1, 2, 3, 2);
+ path.close();
+</div>
+
+<div id="testQuadratic9a">
+ path.moveTo(1.08000004, 0.720000029);
+ path.lineTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(3, 1);
+ path.lineTo(1.01568651, 0.338562161);
+ path.quadTo(1.03542483, 0.541699469, 1.08000004, 0.720000029);
+ path.close();
+ path.moveTo(1.08000004, 0.720000029);
+ path.lineTo(3, 2);
+ path.quadTo(1.39999998, 2, 1.08000004, 0.720000029);
+ path.close();
+
+</div>
+
+<div id="testQuadratic10a">
+path.moveTo(15.5, 15.5);
+path.lineTo(46.5, 15.5);
+path.quadTo(0, 31, 0, 46.5);
+path.lineTo(15.5, 15.5);
+path.close();
+</div>
+
+<div id="testQuadratic10b">
+path.moveTo(5.16666698, 36.1666641);
+path.lineTo(15.5, 15.5);
+path.lineTo(46.5, 15.5);
+path.quadTo(15.5, 25.8333321, 5.16666698, 36.1666641);
+path.close();
+path.moveTo(5.16666698, 36.1666641);
+path.lineTo(0, 46.5);
+path.quadTo(0, 41.3333359, 5.16666698, 36.1666641);
+path.close();
+</div>
+
+<div id="testQuadratic11a">
+path.moveTo(0, 0);
+path.lineTo(15.5, 31);
+path.lineTo(0, 0);
+path.close();
+path.moveTo(0, 15.5);
+path.lineTo(15.5, 15.5);
+path.quadTo(15.5, 15.5, 46.5, 31);
+path.lineTo(0, 15.5);
+path.close();
+</div>
+
+<div id="testQuadratic11b">
+path.moveTo(9.30000019, 18.6000004);
+path.lineTo(0, 15.5);
+path.lineTo(7.75, 15.5);
+path.lineTo(15.5, 15.5);
+path.lineTo(46.5, 31);
+path.lineTo(9.30000019, 18.6000004);
+path.close();
+</div>
+
+<div id="testQuadratic12">
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.lineTo(1, 3);
+ path.close();
+ path.moveTo(1, 0);
+ path.lineTo(0, 1);
+ path.quadTo(1, 1, 0, 3);
+ path.close();
+</div>
+
+<div id="testQuadratic13a">
+path.moveTo(0, 0);
+path.quadTo(0, 0, 15.5, 0);
+path.lineTo(15.5, 31);
+path.lineTo(0, 0);
+path.close();
+path.moveTo(0, 0);
+path.quadTo(15.5, 46.5, 46.5, 46.5);
+path.lineTo(0, 0);
+path.close();
+</div>
+
+<div id="testQuadratic13b">
+path.moveTo(14.8800001, 29.7600002);
+path.quadTo(6.20000029, 18.6000004, 0, 0);
+path.lineTo(14.8800001, 29.7600002);
+path.close();
+path.moveTo(15.5, 30.5437222);
+path.lineTo(15.5, 31);
+path.lineTo(14.8800001, 29.7600002);
+path.quadTo(15.1884346, 30.156559, 15.5, 30.5437222);
+path.close();
+path.moveTo(15.5, 15.5);
+path.lineTo(0, 0);
+path.lineTo(15.5, 0);
+path.lineTo(15.5, 15.5);
+path.close();
+path.moveTo(15.5, 30.5437222);
+path.lineTo(15.5, 15.5);
+path.lineTo(46.5, 46.5);
+path.quadTo(28.34062, 46.5, 15.5, 30.5437222);
+path.close();
+</div>
+
+<div id="testQuadratic14">
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 1, 0);
+ path.lineTo(3, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.quadTo(3, 2, 3, 3);
+ path.close();
+</div>
+
+<div id="testQuadratic15">
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 1, 0);
+ path.lineTo(1, 3);
+ path.close();
+ path.moveTo(1, 0);
+ path.lineTo(0, 1);
+ path.quadTo(1, 1, 0, 3);
+ path.close();
+</div>
+
+<div id="testQuadratic16a">
+path.moveTo(0, 0);
+path.quadTo(0, 0, 31, 0);
+path.lineTo(46.5, 31);
+path.lineTo(0, 0);
+path.close();
+path.moveTo(46.5, 15.5);
+path.lineTo(0, 31);
+path.quadTo(0, 31, 15.5, 31);
+path.lineTo(46.5, 15.5);
+path.close();
+</div>
+
+<div id="testQuadratic16b">
+path.moveTo(31, 20.6666679);
+path.lineTo(0, 0);
+path.lineTo(31, 0);
+path.lineTo(39.8571434, 17.7142868);
+path.lineTo(31, 20.6666679);
+path.close();
+path.moveTo(33.214283, 22.1428585);
+path.lineTo(15.5, 31);
+path.lineTo(0, 31);
+path.lineTo(31, 20.6666679);
+path.lineTo(33.214283, 22.1428585);
+path.close();
+path.moveTo(40.2999992, 18.6000004);
+path.lineTo(46.5, 31);
+path.lineTo(33.214283, 22.1428585);
+path.lineTo(40.2999992, 18.6000004);
+path.close();
+path.moveTo(39.8571434, 17.7142868);
+path.lineTo(46.5, 15.5);
+path.lineTo(40.2999992, 18.6000004);
+path.lineTo(39.8571434, 17.7142868);
+path.close();
+</div>
+
+<div id="testQuadratic17x">
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 3, 1);
+ path.lineTo(0, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.quadTo(3, 1, 0, 2);
+ path.close();
+</div>
+
+<div id="testQuadratic18">
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.lineTo(0, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(1, 0, 1, 1);
+ path.close();
+</div>
+
+<div id="testQuadratic19">
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.lineTo(0, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(2, 0, 0, 1);
+ path.close();
+</div>
+
+<div id="testQuadratic20">
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.lineTo(0, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.close();
+</div>
+
+<div id="testQuadratic21">
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.lineTo(0, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(1, 0, 0, 2);
+ path.close();
+</div>
+
+<div id="testQuadratic22">
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.lineTo(0, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(0, 1, 2, 1);
+ path.close();
+</div>
+
+<div id="testQuadratic23">
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.lineTo(0, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(0, 2, 1, 2);
+ path.close();
+</div>
+
+<div id="testQuadratic24">
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.lineTo(0, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.quadTo(2, 0, 0, 1);
+ path.close();
+</div>
+
+<div id="testQuadratic25">
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 1, 1);
+ path.lineTo(1, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(2, 1, 0, 2);
+ path.close();
+</div>
+
+<div id="testQuadratic26">
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 1, 1);
+ path.lineTo(0, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.close();
+</div>
+
+<div id="testQuadratic27">
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 1, 1);
+ path.lineTo(2, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(2, 1, 0, 2);
+ path.close();
+</div>
+
+<div id="testQuadratic28">
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.lineTo(0, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 2);
+ path.quadTo(1, 2, 0, 3);
+ path.close();
+</div>
+
+<div id="testQuadratic29">
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 2, 1);
+ path.lineTo(0, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.close();
+</div>
+
+<div id="testQuadratic30">
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 1, 2);
+ path.lineTo(1, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.quadTo(0, 1, 1, 2);
+ path.close();
+</div>
+
+<div id="testQuadratic31">
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 1, 2);
+ path.lineTo(1, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.quadTo(0, 1, 1, 3);
+ path.close();
+</div>
+
+<div id="testQuadratic32">
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 2, 3);
+ path.lineTo(2, 3);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(3, 1, 0, 2);
+ path.close();
+</div>
+
+<div id="testQuadratic33">
+ path.moveTo(0, 0);
+ path.quadTo(2, 0, 0, 1);
+ path.lineTo(0, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 1);
+ path.quadTo(2, 1, 2, 2);
+ path.close();
+</div>
+
+<div id="testQuadratic34">
+ path.moveTo(0, 0);
+ path.quadTo(2, 0, 0, 1);
+ path.lineTo(0, 1);
+ path.close();
+ path.moveTo(1, 0);
+ path.lineTo(1, 1);
+ path.quadTo(2, 1, 1, 2);
+ path.close();
+</div>
+
+<div id="testQuadratic35">
+ path.moveTo(0, 0);
+ path.quadTo(0, 1, 1, 1);
+ path.lineTo(1, 3);
+ path.close();
+ path.moveTo(2, 0);
+ path.lineTo(3, 0);
+ path.quadTo(0, 1, 1, 1);
+ path.close();
+</div>
+
+<div id="testQuadratic36">
+ path.moveTo(0, 0);
+ path.quadTo(2, 1, 2, 3);
+ path.lineTo(2, 3);
+ path.close();
+ path.moveTo(3, 1);
+ path.lineTo(1, 2);
+ path.quadTo(3, 2, 1, 3);
+ path.close();
+</div>
+
+<div id="testQuadratic37">
+ path.moveTo(0, 0);
+ path.quadTo(0, 2, 1, 2);
+ path.lineTo(1, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(3, 1);
+ path.quadTo(0, 2, 1, 2);
+ path.close();
+</div>
+
+<div id="testQuadratic38">
+ path.moveTo(1, 0);
+ path.quadTo(0, 1, 1, 1);
+ path.lineTo(1, 1);
+ path.close();
+ path.moveTo(1, 0);
+ path.lineTo(1, 2);
+ path.quadTo(2, 2, 1, 3);
+ path.close();
+</div>
+
+<div id="testQuadratic39">
+path.moveTo(15.5, 0);
+path.quadTo(46.5, 15.5, 46.5, 31);
+path.lineTo(15.5, 0);
+path.close();
+path.moveTo(46.5, 15.5);
+path.lineTo(0, 31);
+path.quadTo(0, 31, 15.5, 31);
+path.lineTo(46.5, 15.5);
+ path.close();
+</div>
+
+<div id="testQuadratic39a">
+path.moveTo(34.875, 19.375);
+path.lineTo(15.5, 0);
+path.quadTo(32.9687576, 8.73437881, 40.5937271, 17.4687576);
+path.lineTo(34.875, 19.375);
+path.close();
+path.moveTo(36.1666641, 20.6666679);
+path.lineTo(15.5, 31);
+path.lineTo(0, 31);
+path.lineTo(34.875, 19.375);
+path.lineTo(36.1666641, 20.6666679);
+path.close();
+path.moveTo(41.1812401, 18.15938);
+path.quadTo(46.5, 24.5796909, 46.5, 31);
+path.lineTo(36.1666641, 20.6666679);
+path.lineTo(41.1812401, 18.15938);
+path.close();
+path.moveTo(40.5937271, 17.4687576);
+path.lineTo(46.5, 15.5);
+path.lineTo(41.1812401, 18.15938);
+path.quadTo(40.8951759, 17.8140678, 40.5937271, 17.4687576);
+ path.close();
+</div>
+
+<div id="testQuadratic40x">
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(2, 0);
+ path.quadTo(3, 0, 2, 2);
+ path.lineTo(3, 2);
+ path.close();
+ path.moveTo(3, 1);
+ path.lineTo(0, 2);
+ path.quadTo(0, 2, 1, 2);
+ path.close();
+</div>
+
+<div id="testQuadratic40xa">
+path.moveTo(31, 0);
+path.quadTo(41.3333359, 0, 37.8888893, 13.7777777);
+path.lineTo(31, 0);
+path.close();
+path.moveTo(37.8888893, 13.7777777);
+path.quadTo(37.2993202, 16.1360455, 36.3061028, 18.8979664);
+path.lineTo(0, 31);
+path.lineTo(15.5, 31);
+path.lineTo(35.5182915, 20.9908543);
+path.quadTo(33.7454262, 25.5091457, 31, 31);
+path.lineTo(46.5, 31);
+path.lineTo(40.2999992, 18.6000004);
+path.lineTo(46.5, 15.5);
+path.lineTo(39.8571434, 17.7142868);
+path.lineTo(37.8888893, 13.7777777);
+path.close();
+path.moveTo(36.3061028, 18.8979664);
+path.quadTo(35.9396667, 19.9169388, 35.5182915, 20.9908543);
+path.lineTo(40.2999992, 18.6000004);
+path.lineTo(39.8571434, 17.7142868);
+path.lineTo(36.3061028, 18.8979664);
+</div>
+
+<div id="testQuadratic40xb">
+path.moveTo(31, 0);
+path.quadTo(46.5, 0, 31, 31);
+path.lineTo(46.5, 31);
+path.lineTo(31, 0);
+path.close();
+path.moveTo(46.5, 15.5);
+path.lineTo(0, 31);
+path.quadTo(0, 31, 15.5, 31);
+path.lineTo(46.5, 15.5);
+path.close();
+</div>
+
+<div id="testQuadratic41o">
+path.moveTo(419.33905, 236.377808);
+path.quadTo(398.847778, 242.58728, 384.255524, 242.58728);
+path.quadTo(359.417633, 242.58728, 343.738708, 226.080429);
+path.quadTo(328.059784, 209.573578, 328.059784, 183.286819);
+path.quadTo(328.059784, 157.724487, 341.875854, 141.372879);
+path.quadTo(355.691956, 125.021263, 377.218109, 125.021263);
+path.quadTo(397.605896, 125.021263, 408.731201, 139.51004);
+path.quadTo(419.856506, 153.99881, 419.856506, 180.699539);
+path.lineTo(419.752991, 187.012497);
+path.lineTo(348.861511, 187.012497);
+path.quadTo(353.311646, 227.063599, 388.084686, 227.063599);
+path.quadTo(400.814117, 227.063599, 419.33905, 220.233185);
+path.lineTo(419.33905, 236.377808);
+path.close();
+path.moveTo(349.792938, 171.695801);
+path.lineTo(399.365234, 171.695801);
+path.quadTo(399.365234, 140.337967, 375.976227, 140.337967);
+path.quadTo(352.483704, 140.337967, 349.792938, 171.695801);
+path.close();
+path.moveTo(378.682587, 277.360321);
+path.lineTo(381.062897, 259.66333);
+path.quadTo(398.759888, 268.046112, 415.939423, 268.046112);
+path.quadTo(450.402008, 268.046112, 450.402008, 231.513718);
+path.lineTo(450.402008, 213.816727);
+path.quadTo(439.12146, 237.41272, 413.352142, 237.41272);
+path.quadTo(393.171356, 237.41272, 381.269867, 222.716965);
+path.quadTo(369.368378, 208.02121, 369.368378, 183.079834);
+path.quadTo(369.368378, 157.414017, 382.92572, 141.269379);
+path.quadTo(396.483093, 125.124756, 418.009247, 125.124756);
+path.quadTo(436.844666, 125.124756, 450.402008, 140.441467);
+path.lineTo(450.402008, 127.608543);
+path.lineTo(470.89325, 127.608543);
+path.lineTo(470.89325, 209.366608);
+path.quadTo(470.89325, 235.756866, 468.150757, 248.43454);
+path.quadTo(465.408234, 261.112213, 457.853363, 269.184509);
+path.quadTo(444.502991, 283.362823, 416.353394, 283.362823);
+path.quadTo(396.690063, 283.362823, 378.682587, 277.360321);
+path.close();
+path.moveTo(450.402008, 201.087311);
+path.lineTo(450.402008, 154.412781);
+path.quadTo(436.948151, 140.441467, 421.113983, 140.441467);
+path.quadTo(407.039185, 140.441467, 399.070374, 151.722);
+path.quadTo(391.101532, 163.002533, 391.101532, 182.665863);
+path.quadTo(391.101532, 219.612228, 417.07782, 219.612228);
+path.quadTo(434.774841, 219.612228, 450.402008, 201.087311);
+path.close();
+path.moveTo(482.9328, 236.377808);
+path.quadTo(462.441528, 242.58728, 447.849274, 242.58728);
+path.quadTo(423.011383, 242.58728, 407.332458, 226.080429);
+path.quadTo(391.653534, 209.573578, 391.653534, 183.286819);
+path.quadTo(391.653534, 157.724487, 405.469604, 141.372879);
+path.quadTo(419.285706, 125.021263, 440.811859, 125.021263);
+path.quadTo(461.199646, 125.021263, 472.324951, 139.51004);
+path.quadTo(483.450256, 153.99881, 483.450256, 180.699539);
+path.lineTo(483.346741, 187.012497);
+path.lineTo(412.455261, 187.012497);
+path.quadTo(416.905396, 227.063599, 451.678436, 227.063599);
+path.quadTo(464.407867, 227.063599, 482.9328, 220.233185);
+path.lineTo(482.9328, 236.377808);
+path.close();
+path.moveTo(413.386688, 171.695801);
+path.lineTo(462.958984, 171.695801);
+path.quadTo(462.958984, 140.337967, 439.569977, 140.337967);
+path.quadTo(416.077454, 140.337967, 413.386688, 171.695801);
+path.close();
+</div>
+
+<div id="testQuadratic41s">
+path.moveTo(341.875854, 141.372879);
+path.quadTo(355.691956,125.021263, 377.218109,125.021263);
+path.quadTo(388.787811,125.021263, 397.374664,129.687164);
+path.quadTo(406.565979,125.124756, 418.009247,125.124756);
+path.quadTo(423.583374,125.124756, 428.695251,126.466187);
+path.quadTo(434.412903,125.021263, 440.811859,125.021263);
+path.quadTo(449.427277,125.021263, 456.388672,127.608543);
+path.lineTo(470.89325,127.608543);
+path.lineTo(470.89325,137.749908);
+path.quadTo(471.627319,138.601486, 472.324951,139.51004);
+path.quadTo(483.450256,153.99881, 483.450256,180.699539);
+path.lineTo(483.346741,187.012497);
+path.lineTo(470.89325,187.012497);
+path.lineTo(470.89325,209.366608);
+path.quadTo(470.89325,217.414856, 470.638184,224.187729);
+path.quadTo(476.428223,222.631516, 482.9328,220.233185);
+path.lineTo(482.9328,236.377808);
+path.quadTo(475.87207,238.517426, 469.511749,239.919785);
+path.quadTo(468.946777,244.754791, 468.150757,248.43454);
+path.quadTo(465.408234,261.112213, 457.853363,269.184509);
+path.quadTo(444.502991,283.362823, 416.353394,283.362823);
+path.quadTo(396.690063,283.362823, 378.682587,277.360321);
+path.lineTo(381.062897,259.66333);
+path.quadTo(398.759888,268.046112, 415.939423,268.046112);
+path.quadTo(444.719147,268.046112, 449.464905,242.568665);
+path.quadTo(448.648254,242.58728, 447.849274,242.58728);
+path.quadTo(433.084625,242.58728, 421.556366,236.754425);
+path.quadTo(418.89566,237.203537, 416.046783,237.346252);
+path.quadTo(397.661652,242.58728, 384.255524,242.58728);
+path.quadTo(359.417633,242.58728, 343.738708,226.080429);
+path.quadTo(328.059784,209.573578, 328.059784,183.286819);
+path.quadTo(328.059784,157.724487, 341.875854,141.372879);
+path.close();
+path.moveTo(442.014923, 226.179474);
+path.quadTo(445.951935,226.953491, 450.402008,227.049881);
+path.lineTo(450.402008,213.816727);
+path.quadTo(446.904755,221.132065, 442.014923,226.179474);
+path.close();
+path.moveTo(395.347717, 206.501785);
+path.quadTo(392.200165,197.593536, 391.734406,187.012497);
+path.lineTo(391.197113,187.012497);
+path.quadTo(391.738647,198.938644, 395.347717,206.501785);
+path.close();
+path.moveTo(391.808533, 171.695801);
+path.lineTo(392.428436,171.695801);
+path.quadTo(393.693451,162.656265, 397.02359,154.9935);
+path.quadTo(397.023804,154.992996, 397.024048,154.992493);
+path.quadTo(393.175995,143.845093, 383.003235,141.177292);
+path.quadTo(382.964447,141.223267, 382.92572,141.269379);
+</div>
+
+<div id="testQuadratic42o">
+path.moveTo(421.962158, 236.285355);
+path.quadTo(400.947845, 242.65332, 385.983124, 242.65332);
+path.quadTo(360.511261, 242.65332, 344.432129, 225.725143);
+path.quadTo(328.352997, 208.796951, 328.352997, 181.839218);
+path.quadTo(328.352997, 155.62442, 342.521729, 138.855438);
+path.quadTo(356.69046, 122.086449, 378.766083, 122.086449);
+path.quadTo(399.674255, 122.086449, 411.083527, 136.945038);
+path.quadTo(422.492798, 151.803635, 422.492798, 179.185898);
+path.lineTo(422.386688, 185.660004);
+path.lineTo(349.685699, 185.660004);
+path.quadTo(354.24942, 226.733398, 389.910034, 226.733398);
+path.quadTo(402.964386, 226.733398, 421.962158, 219.728638);
+path.lineTo(421.962158, 236.285355);
+path.close();
+path.moveTo(350.6409, 169.952347);
+path.lineTo(401.478516, 169.952347);
+path.quadTo(401.478516, 137.794098, 377.492493, 137.794098);
+path.quadTo(353.40036, 137.794098, 350.6409, 169.952347);
+path.close();
+path.moveTo(379.213562, 278.313934);
+path.lineTo(381.654602, 260.165222);
+path.quadTo(399.803314, 268.761993, 417.421356, 268.761993);
+path.quadTo(452.763611, 268.761993, 452.763611, 231.297104);
+path.lineTo(452.763611, 213.148392);
+path.quadTo(441.195129, 237.34668, 414.768036, 237.34668);
+path.quadTo(394.072144, 237.34668, 381.866882, 222.275818);
+path.quadTo(369.661591, 207.204956, 369.661591, 181.626953);
+path.quadTo(369.661591, 155.306015, 383.565002, 138.749298);
+path.quadTo(397.468384, 122.192581, 419.544037, 122.192581);
+path.quadTo(438.860199, 122.192581, 452.763611, 137.900238);
+path.lineTo(452.763611, 124.739769);
+path.lineTo(473.777893, 124.739769);
+path.lineTo(473.777893, 208.584686);
+path.quadTo(473.777893, 235.64856, 470.965363, 248.649826);
+path.quadTo(468.152863, 261.651093, 460.405151, 269.929443);
+path.quadTo(446.71402, 284.469666, 417.845886, 284.469666);
+path.quadTo(397.680664, 284.469666, 379.213562, 278.313934);
+path.close();
+path.moveTo(452.763611, 200.094055);
+path.lineTo(452.763611, 152.228165);
+path.quadTo(438.966339, 137.900238, 422.727997, 137.900238);
+path.quadTo(408.293945, 137.900238, 400.121704, 149.468719);
+path.quadTo(391.949493, 161.037186, 391.949493, 181.202423);
+path.quadTo(391.949493, 219.091827, 418.588837, 219.091827);
+path.quadTo(436.737549, 219.091827, 452.763611, 200.094055);
+path.close();
+path.moveTo(485.555908, 236.285355);
+path.quadTo(464.541595, 242.65332, 449.576874, 242.65332);
+path.quadTo(424.105011, 242.65332, 408.025879, 225.725143);
+path.quadTo(391.946747, 208.796951, 391.946747, 181.839218);
+path.quadTo(391.946747, 155.62442, 406.115479, 138.855438);
+path.quadTo(420.28421, 122.086449, 442.359833, 122.086449);
+path.quadTo(463.268005, 122.086449, 474.677277, 136.945038);
+path.quadTo(486.086548, 151.803635, 486.086548, 179.185898);
+path.lineTo(485.980438, 185.660004);
+path.lineTo(413.279449, 185.660004);
+path.quadTo(417.84317, 226.733398, 453.503784, 226.733398);
+path.quadTo(466.558136, 226.733398, 485.555908, 219.728638);
+path.lineTo(485.555908, 236.285355);
+path.close();
+path.moveTo(414.23465, 169.952347);
+path.lineTo(465.072266, 169.952347);
+path.quadTo(465.072266, 137.794098, 441.086243, 137.794098);
+path.quadTo(416.99411, 137.794098, 414.23465, 169.952347);
+path.close();
+</div>
+
+<div id="testQuadratic42s">
+path.moveTo(342.521729, 138.855438);
+path.quadTo(356.69046,122.086449, 378.766083,122.086449);
+path.quadTo(390.293488,122.086449, 398.933502,126.603004);
+path.quadTo(408.150299,122.192581, 419.544037,122.192581);
+path.quadTo(425.108429,122.192581, 430.223633,123.496056);
+path.quadTo(435.959412,122.086449, 442.359833,122.086449);
+path.quadTo(451.19516,122.086449, 458.334229,124.739769);
+path.lineTo(473.777893,124.739769);
+path.lineTo(473.777893,135.814713);
+path.quadTo(474.234741,136.368698, 474.677277,136.945038);
+path.quadTo(486.086548,151.803635, 486.086548,179.185898);
+path.lineTo(485.980438,185.660004);
+path.lineTo(473.777893,185.660004);
+path.lineTo(473.777893,208.584686);
+path.quadTo(473.777893,216.745773, 473.522156,223.628113);
+path.quadTo(479.207153,222.069519, 485.555908,219.728638);
+path.lineTo(485.555908,236.285355);
+path.quadTo(478.638306,238.381592, 472.376221,239.787796);
+path.quadTo(471.792389,244.826782, 470.965363,248.649826);
+path.quadTo(468.152863,261.651093, 460.405151,269.929443);
+path.quadTo(446.71402,284.469666, 417.845886,284.469666);
+path.quadTo(397.680664,284.469666, 379.213562,278.313934);
+path.lineTo(381.654602,260.165222);
+path.quadTo(399.803314,268.761993, 417.421356,268.761993);
+path.quadTo(446.944275,268.761993, 451.80542,242.619034);
+path.quadTo(450.674866,242.65332, 449.576874,242.65332);
+path.quadTo(434.524628,242.65332, 422.75238,236.741913);
+path.quadTo(420.864227,237.041901, 418.884674,237.193085);
+path.quadTo(399.840271,242.65332, 385.983124,242.65332);
+path.quadTo(360.511261,242.65332, 344.432129,225.725143);
+path.quadTo(328.352997,208.796951, 328.352997,181.839218);
+path.quadTo(328.352997,155.62442, 342.521729,138.855438);
+path.close();
+path.moveTo(383.823944, 138.443222);
+path.quadTo(380.900299,137.794098, 377.492493,137.794098);
+path.quadTo(353.40036,137.794098, 350.6409,169.952347);
+path.lineTo(370.408203,169.952347);
+path.quadTo(372.883484,151.469254, 383.565002,138.749298);
+path.quadTo(383.694122,138.595551, 383.823944,138.443222);
+path.close();
+path.moveTo(369.740021, 185.660004);
+path.lineTo(349.685699,185.660004);
+path.quadTo(353.983734,224.342361, 385.863525,226.594208);
+path.quadTo(383.762756,224.616837, 381.866882,222.275818);
+path.quadTo(370.639954,208.41301, 369.740021,185.660004);
+path.close();
+path.moveTo(413.279449, 185.660004);
+path.quadTo(415.737305,207.780716, 427.214905,217.987976);
+path.quadTo(440.600586,214.512451, 452.763611,200.094055);
+path.lineTo(452.763611,185.660004);
+</div>
+
+<div id="testQuadratic43o">
+path.moveTo(288.755981, 240);
+path.lineTo(288.755981, 102.232819);
+path.lineTo(315.843994, 102.232819);
+path.lineTo(354.009216, 208.816208);
+path.lineTo(393.291473, 102.232819);
+path.lineTo(417.493835, 102.232819);
+path.lineTo(417.493835, 240);
+path.lineTo(399.248962, 240);
+path.lineTo(399.248962, 127.92453);
+path.lineTo(361.269928, 230.784485);
+path.lineTo(342.373474, 230.784485);
+path.lineTo(305.511444, 127.645271);
+path.lineTo(305.511444, 240);
+path.lineTo(288.755981, 240);
+path.close();
+path.moveTo(397.864014, 236.741989);
+path.quadTo(379.433014, 242.327148, 366.307892, 242.327148);
+path.quadTo(343.967255, 242.327148, 329.864746, 227.479935);
+path.quadTo(315.762238, 212.632736, 315.762238, 188.988907);
+path.quadTo(315.762238, 165.996674, 328.189209, 151.289093);
+path.quadTo(340.61618, 136.581512, 359.978058, 136.581512);
+path.quadTo(378.315979, 136.581512, 388.322723, 149.613556);
+path.quadTo(398.329468, 162.645584, 398.329468, 186.661758);
+path.lineTo(398.236359, 192.339996);
+path.lineTo(334.472504, 192.339996);
+path.quadTo(338.475189, 228.364258, 369.752075, 228.364258);
+path.quadTo(381.20163, 228.364258, 397.864014, 222.220581);
+path.lineTo(397.864014, 236.741989);
+path.close();
+path.moveTo(335.310272, 178.563278);
+path.lineTo(379.898438, 178.563278);
+path.quadTo(379.898438, 150.358246, 358.861023, 150.358246);
+path.quadTo(337.730499, 150.358246, 335.310272, 178.563278);
+path.close();
+path.moveTo(346.052765, 240);
+path.lineTo(346.052765, 138.908661);
+path.lineTo(364.390686, 138.908661);
+path.lineTo(364.390686, 157.898193);
+path.quadTo(375.281769, 136.674606, 396.039917, 136.674606);
+path.quadTo(398.832489, 136.674606, 401.904327, 137.140045);
+path.lineTo(401.904327, 154.267853);
+path.quadTo(397.156952, 152.685394, 393.526611, 152.685394);
+path.quadTo(376.119537, 152.685394, 364.390686, 173.350464);
+path.lineTo(364.390686, 240);
+path.lineTo(346.052765, 240);
+path.close();
+path.moveTo(362.792297, 273.604034);
+path.lineTo(364.933289, 257.68634);
+path.quadTo(380.850983, 265.226288, 396.303253, 265.226288);
+path.quadTo(427.300842, 265.226288, 427.300842, 232.366959);
+path.lineTo(427.300842, 216.449265);
+path.quadTo(417.15448, 237.672852, 393.976105, 237.672852);
+path.quadTo(375.824341, 237.672852, 365.119446, 224.454651);
+path.quadTo(354.414581, 211.23645, 354.414581, 188.802734);
+path.quadTo(354.414581, 165.717422, 366.608826, 151.196014);
+path.quadTo(378.803101, 136.674606, 398.164948, 136.674606);
+path.quadTo(415.106598, 136.674606, 427.300842, 150.451324);
+path.lineTo(427.300842, 138.908661);
+path.lineTo(445.731873, 138.908661);
+path.lineTo(445.731873, 212.446564);
+path.quadTo(445.731873, 236.183472, 443.265106, 247.586502);
+path.quadTo(440.798309, 258.989532, 434.003052, 266.250244);
+path.quadTo(421.994965, 279.002991, 396.675598, 279.002991);
+path.quadTo(378.989258, 279.002991, 362.792297, 273.604034);
+path.close();
+path.moveTo(427.300842, 204.999695);
+path.lineTo(427.300842, 163.017929);
+path.quadTo(415.199677, 150.451324, 400.95755, 150.451324);
+path.quadTo(388.297852, 150.451324, 381.130249, 160.597687);
+path.quadTo(373.962616, 170.744064, 373.962616, 188.430389);
+path.quadTo(373.962616, 221.662079, 397.327179, 221.662079);
+path.quadTo(413.244873, 221.662079, 427.300842, 204.999695);
+path.close();
+path.moveTo(461.457764, 236.741989);
+path.quadTo(443.026764, 242.327148, 429.901642, 242.327148);
+path.quadTo(407.561005, 242.327148, 393.458496, 227.479935);
+path.quadTo(379.355988, 212.632736, 379.355988, 188.988907);
+path.quadTo(379.355988, 165.996674, 391.782959, 151.289093);
+path.quadTo(404.20993, 136.581512, 423.571808, 136.581512);
+path.quadTo(441.909729, 136.581512, 451.916473, 149.613556);
+path.quadTo(461.923218, 162.645584, 461.923218, 186.661758);
+path.lineTo(461.830109, 192.339996);
+path.lineTo(398.066254, 192.339996);
+path.quadTo(402.068939, 228.364258, 433.345825, 228.364258);
+path.quadTo(444.79538, 228.364258, 461.457764, 222.220581);
+path.lineTo(461.457764, 236.741989);
+path.close();
+path.moveTo(398.904022, 178.563278);
+path.lineTo(443.492188, 178.563278);
+path.quadTo(443.492188, 150.358246, 422.454773, 150.358246);
+path.quadTo(401.324249, 150.358246, 398.904022, 178.563278);
+path.close();
+</div>
+
+<div id="testQuadratic43s">
+path.moveTo(288.755981, 240);
+path.lineTo(288.755981,102.232819);
+path.lineTo(315.843994,102.232819);
+path.lineTo(331.979736,147.294876);
+path.quadTo(343.453125,136.581512, 359.978058,136.581512);
+path.quadTo(370.869446,136.581512, 378.822021,141.178574);
+path.quadTo(378.893585,141.140915, 378.965302,141.103577);
+path.lineTo(393.291473,102.232819);
+path.lineTo(417.493835,102.232819);
+path.lineTo(417.493835,136.965759);
+path.quadTo(420.44223,136.581512, 423.571808,136.581512);
+path.quadTo(431.320984,136.581512, 437.582458,138.908661);
+path.lineTo(445.731873,138.908661);
+path.lineTo(445.731873,143.392502);
+path.quadTo(449.143951,146.002823, 451.916473,149.613556);
+path.quadTo(461.923218,162.645584, 461.923218,186.661758);
+path.lineTo(461.830109,192.339996);
+path.lineTo(445.731873,192.339996);
+path.lineTo(445.731873,212.446564);
+path.quadTo(445.731873,220.39856, 445.455017,226.966339);
+path.quadTo(452.7435,225.43367, 461.457764,222.220581);
+path.lineTo(461.457764,236.741989);
+path.quadTo(452.250824,239.531982, 444.367889,240.928268);
+path.quadTo(443.897583,244.662796, 443.265106,247.586502);
+path.quadTo(440.798309,258.989532, 434.003052,266.250244);
+path.quadTo(421.994965,279.002991, 396.675598,279.002991);
+path.quadTo(378.989258,279.002991, 362.792297,273.604034);
+path.lineTo(364.933289,257.68634);
+path.quadTo(380.850983,265.226288, 396.303253,265.226288);
+path.quadTo(422.230743,265.226288, 426.471558,242.237076);
+path.quadTo(419.570892,241.869324, 413.503387,240);
+path.lineTo(399.248962,240);
+path.lineTo(399.248962,237.37915);
+path.quadTo(397.047638,237.633072, 394.711517,237.667465);
+path.quadTo(378.296356,242.327148, 366.307892,242.327148);
+path.quadTo(357.463165,242.327148, 349.909637,240);
+path.lineTo(346.052765,240);
+path.lineTo(346.052765,238.625916);
+path.quadTo(336.926056,234.914124, 329.864746,227.479935);
+path.quadTo(315.762238,212.632736, 315.762238,188.988907);
+path.quadTo(315.762238,176.540054, 319.405273,166.519882);
+path.lineTo(305.511444,127.645271);
+path.lineTo(305.511444,240);
+path.lineTo(288.755981,240);
+path.close();
+path.moveTo(375.464813, 192.339996);
+path.lineTo(374.267029,195.583939);
+path.quadTo(375.987579,214.575378, 387.432068,219.736267);
+path.quadTo(380.122528,208.101486, 379.428741,192.339996);
+path.lineTo(375.464813,192.339996);
+path.close();
+path.moveTo(427.300842, 178.563278);
+path.lineTo(427.300842,163.017929);
+path.quadTo(422.561523,158.096329, 417.493835,155.102234);
+path.lineTo(417.493835,178.563278);
+path.lineTo(427.300842,178.563278);
+path.close();
+path.moveTo(427.300842, 192.339996);
+path.lineTo(417.493835,192.339996);
+path.lineTo(417.493835,214.429169);
+path.quadTo(422.505676,210.684036, 427.300842,204.999695);
+path.lineTo(427.300842,192.339996);
+path.close();
+path.moveTo(420.700134, 226.556015);
+path.quadTo(423.794342,227.54834, 427.300842,227.996094);
+path.lineTo(427.300842,216.449265);
+path.quadTo(424.497772,222.312531, 420.700134,226.556015);
+path.close();
+path.moveTo(368.744965, 228.354782);
+path.quadTo(366.836426,226.574738, 365.119446,224.454651);
+path.quadTo(364.748657,223.996796, 364.390686,223.527878);
+path.lineTo(364.390686,228.077774);
+path.quadTo(366.495239,228.312164, 368.744965,228.354782);
+path.close();
+path.moveTo(399.248962, 199.701065);
+path.lineTo(399.248962,192.339996);
+path.lineTo(398.236359,192.339996);
+path.lineTo(398.066254,192.339996);
+path.quadTo(398.498535,196.230621, 399.248962,199.701065);
+path.close();
+path.moveTo(399.248962, 178.563278);
+path.lineTo(399.248962,175.376892);
+path.quadTo(399.04483,176.922348, 398.904022,178.563278);
+path.lineTo(399.248962,178.563278);
+path.close();
+path.moveTo(399.248962, 136.688828);
+path.lineTo(399.248962,127.92453);
+path.lineTo(396.018158,136.674606);
+path.quadTo(396.029053,136.674606, 396.039917,136.674606);
+path.quadTo(396.513672,136.674606, 396.995453,136.688004);
+path.quadTo(397.576904,136.674606, 398.164948,136.674606);
+path.quadTo(398.709412,136.674606, 399.248962,136.688828);
+path.close();
+path.moveTo(346.052765, 178.563278);
+path.lineTo(346.052765,154.02713);
+path.quadTo(340.97113,157.621338, 338.22525,164.736588);
+path.lineTo(343.1763,178.563278);
+path.lineTo(346.052765,178.563278);
+path.close();
+path.moveTo(364.390686, 150.922379);
+path.lineTo(364.390686,154.048065);
+path.quadTo(365.340851,152.726639, 366.38147,151.468765);
+path.quadTo(365.420258,151.14975, 364.390686,150.922379);
+path.close();
+path.moveTo(367.863586, 152.032623);
+path.quadTo(367.144043,151.721848, 366.38147,151.468765);
+</div>
+
+<div id="testQuadratic44o">
+path.moveTo(354.009216, 208.816208);
+path.lineTo(393.291473, 102.232819);
+path.lineTo(399.248962, 127.92453);
+path.lineTo(361.269928, 230.784485);
+path.lineTo(354.009216, 208.816208);
+path.close();
+path.moveTo(328.189209, 151.289093);
+path.quadTo(340.61618, 136.581512, 359.978058, 136.581512);
+path.quadTo(378.315979, 136.581512, 388.322723, 149.613556);
+path.lineTo(328.189209, 151.289093);
+path.close();
+path.moveTo(346.052765, 138.908661);
+path.lineTo(364.390686, 138.908661);
+path.lineTo(364.390686, 157.898193);
+path.quadTo(375.281769, 136.674606, 396.039917, 136.674606);
+path.lineTo(346.052765, 138.908661);
+path.close();
+</div>
+
+<div id="testQuadratic44s">
+path.moveTo(380.33902, 137.376312);
+path.lineTo(393.291473,102.232819);
+path.lineTo(399.248962,127.92453);
+path.lineTo(396.018158,136.674606);
+path.quadTo(396.029053,136.674606, 396.039917,136.674606);
+path.lineTo(396.017792,136.675598);
+path.lineTo(361.269928,230.784485);
+path.lineTo(354.009216,208.816208);
+path.lineTo(375.699249,149.965286);
+path.lineTo(369.22699,150.14563);
+path.quadTo(373.524384,144.511566, 378.917297,141.233871);
+path.lineTo(380.33902,137.376312);
+path.close();
+path.moveTo(380.33902, 137.376312);
+path.lineTo(378.917297,141.233856);
+path.quadTo(375.048248,138.978912, 370.480499,137.816925);
+path.lineTo(380.33902,137.376312);
+path.close();
+path.moveTo(392.55661, 136.830276);
+path.lineTo(380.33902,137.376312);
+</div>
+
+<div id="testQuadratic45o">
+path.moveTo(315.843994, 102.232819);
+path.lineTo(354.009216, 208.816208);
+path.lineTo(393.291473, 102.232819);
+path.lineTo(399.248962, 127.92453);
+path.lineTo(361.269928, 230.784485);
+path.lineTo(342.373474, 230.784485);
+path.lineTo(305.511444, 127.645271);
+path.lineTo(315.843994, 102.232819);
+path.close();
+path.moveTo(366.307892, 242.327148);
+path.quadTo(343.967255, 242.327148, 329.864746, 227.479935);
+path.quadTo(315.762238, 212.632736, 315.762238, 188.988907);
+path.quadTo(315.762238, 165.996674, 328.189209, 151.289093);
+path.quadTo(340.61618, 136.581512, 359.978058, 136.581512);
+path.quadTo(378.315979, 136.581512, 388.322723, 149.613556);
+path.quadTo(398.329468, 162.645584, 398.329468, 186.661758);
+path.lineTo(398.236359, 192.339996);
+path.lineTo(334.472504, 192.339996);
+path.quadTo(338.475189, 228.364258, 369.752075, 228.364258);
+path.quadTo(381.20163, 228.364258, 397.864014, 222.220581);
+path.lineTo(366.307892, 242.327148);
+path.close();
+path.moveTo(335.310272, 178.563278);
+path.lineTo(379.898438, 178.563278);
+path.quadTo(379.898438, 150.358246, 358.861023, 150.358246);
+path.quadTo(337.730499, 150.358246, 335.310272, 178.563278);
+path.close();
+path.moveTo(346.052765, 240);
+path.lineTo(346.052765, 138.908661);
+path.lineTo(364.390686, 138.908661);
+path.lineTo(364.390686, 157.898193);
+path.quadTo(375.281769, 136.674606, 396.039917, 136.674606);
+path.lineTo(401.904327, 154.267853);
+path.quadTo(397.156952, 152.685394, 393.526611, 152.685394);
+path.quadTo(376.119537, 152.685394, 364.390686, 173.350464);
+path.lineTo(364.390686, 240);
+path.lineTo(346.052765, 240);
+path.close();
+path.moveTo(396.303253, 265.226288);
+path.quadTo(427.300842, 265.226288, 427.300842, 232.366959);
+path.lineTo(427.300842, 216.449265);
+path.quadTo(417.15448, 237.672852, 393.976105, 237.672852);
+path.quadTo(375.824341, 237.672852, 365.119446, 224.454651);
+path.quadTo(354.414581, 211.23645, 354.414581, 188.802734);
+path.quadTo(354.414581, 165.717422, 366.608826, 151.196014);
+path.quadTo(378.803101, 136.674606, 398.164948, 136.674606);
+path.lineTo(396.303253, 265.226288);
+path.close();
+path.moveTo(400.95755, 150.451324);
+path.quadTo(388.297852, 150.451324, 381.130249, 160.597687);
+path.quadTo(373.962616, 170.744064, 373.962616, 188.430389);
+path.quadTo(373.962616, 221.662079, 397.327179, 221.662079);
+path.lineTo(400.95755, 150.451324);
+path.close();
+path.moveTo(429.901642, 242.327148);
+path.quadTo(407.561005, 242.327148, 393.458496, 227.479935);
+path.quadTo(379.355988, 212.632736, 379.355988, 188.988907);
+path.quadTo(379.355988, 165.996674, 391.782959, 151.289093);
+path.quadTo(404.20993, 136.581512, 423.571808, 136.581512);
+path.lineTo(429.901642, 242.327148);
+path.close();
+</div>
+
+<div id="testQuadratic45s">
+path.moveTo(305.511444, 127.645271);
+path.lineTo(315.843994,102.232819);
+path.lineTo(331.979736,147.294876);
+path.quadTo(343.453125,136.581512, 359.978058,136.581512);
+path.quadTo(370.869446,136.581512, 378.822021,141.178574);
+path.quadTo(378.893585,141.140915, 378.965302,141.103577);
+path.lineTo(393.291473,102.232819);
+path.lineTo(399.248962,127.92453);
+path.lineTo(396.018158,136.674606);
+path.quadTo(396.029053,136.674606, 396.039917,136.674606);
+path.lineTo(396.054596,136.718628);
+path.quadTo(397.098907,136.674606, 398.164948,136.674606);
+path.lineTo(398.076477,142.784256);
+path.lineTo(398.697632,144.647751);
+path.quadTo(409.233032,136.581512, 423.571808,136.581512);
+path.lineTo(429.901642,242.327148);
+path.quadTo(428.161621,242.327148, 426.471558,242.237076);
+path.quadTo(427.300842,237.741562, 427.300842,232.366959);
+path.lineTo(427.300842,216.449265);
+path.quadTo(419.710114,232.327133, 404.8255,236.326401);
+path.quadTo(400.557983,233.971252, 396.803375,230.691772);
+path.lineTo(396.7034,237.596863);
+path.quadTo(395.363068,237.672852, 393.976105,237.672852);
+path.quadTo(385.309937,237.672852, 378.341187,234.659912);
+path.lineTo(366.307892,242.327148);
+path.quadTo(357.463165,242.327148, 349.909637,240);
+path.lineTo(346.052765,240);
+path.lineTo(346.052765,238.625916);
+path.quadTo(336.926056,234.914124, 329.864746,227.479935);
+path.quadTo(315.762238,212.632736, 315.762238,188.988907);
+path.quadTo(315.762238,176.540054, 319.405273,166.519882);
+path.lineTo(305.511444,127.645271);
+path.close();
+path.moveTo(375.464813, 192.339996);
+path.lineTo(374.267029,195.583939);
+path.quadTo(375.987579,214.575378, 387.432068,219.736267);
+path.quadTo(380.122528,208.101486, 379.428741,192.339996);
+path.lineTo(375.464813,192.339996);
+path.close();
+path.moveTo(397.925934, 153.178131);
+path.lineTo(397.615479,174.615356);
+path.quadTo(398.329468,180.246704, 398.329468,186.661758);
+path.lineTo(398.236359,192.339996);
+path.lineTo(397.358795,192.339996);
+path.lineTo(396.934174,221.659714);
+path.quadTo(397.129852,221.662079, 397.327179,221.662079);
+path.lineTo(400.781189,153.910889);
+path.quadTo(399.295654,153.462463, 397.925934,153.178131);
+path.close();
+path.moveTo(400.914398, 151.298019);
+path.lineTo(400.632721,150.453003);
+path.quadTo(400.794678,150.451324, 400.95755,150.451324);
+path.lineTo(400.914398,151.298019);
+path.close();
+path.moveTo(368.744965, 228.354782);
+path.quadTo(366.836426,226.574738, 365.119446,224.454651);
+path.quadTo(364.748657,223.996796, 364.390686,223.527878);
+path.lineTo(364.390686,228.077774);
+path.quadTo(366.495239,228.312164, 368.744965,228.354782);
+path.close();
+path.moveTo(346.052765, 178.563278);
+path.lineTo(346.052765,154.02713);
+path.quadTo(340.97113,157.621338, 338.22525,164.736588);
+path.lineTo(343.1763,178.563278);
+path.lineTo(346.052765,178.563278);
+path.close();
+path.moveTo(364.390686, 150.922379);
+path.lineTo(364.390686,154.048065);
+path.quadTo(365.340851,152.726639, 366.38147,151.468765);
+path.quadTo(365.420258,151.14975, 364.390686,150.922379);
+path.close();
+path.moveTo(367.863586, 152.032623);
+path.quadTo(367.144043,151.721848, 366.38147,151.468765);
+</div>
+
+<div id="testQuadratic46o">
+path.moveTo(366.608826, 151.196014);
+path.quadTo(378.803101, 136.674606, 398.164948, 136.674606);
+path.lineTo(354.009216, 208.816208);
+path.lineTo(393.291473, 102.232819);
+path.lineTo(359.978058, 136.581512);
+path.quadTo(378.315979, 136.581512, 388.322723, 149.613556);
+path.lineTo(364.390686, 157.898193);
+path.quadTo(375.281769, 136.674606, 396.039917, 136.674606);
+path.lineTo(350, 120);
+path.lineTo(366.608826, 151.196014);
+path.close();
+</div>
+
+<div id="testQuadratic46s">
+path.moveTo(369.285553, 126.984779);
+path.lineTo(393.291473,102.232819);
+path.lineTo(382.416199,131.740402);
+path.lineTo(396.039917,136.674606);
+path.quadTo(387.290802,136.674606, 380.294495,140.44487);
+path.quadTo(379.623352,140.760971, 378.965302,141.103577);
+path.lineTo(378.917297,141.233856);
+path.quadTo(378.86972,141.206131, 378.822021,141.178574);
+path.quadTo(372.011871,144.761871, 366.608826,151.196014);
+path.lineTo(350,120);
+path.lineTo(369.285553,126.984779);
+path.close();
+path.moveTo(374.00174, 154.571106);
+path.lineTo(378.917297,141.233871);
+path.quadTo(378.917297,141.233871, 378.917297,141.233856);
+path.quadTo(384.294891,144.368011, 388.322723,149.613556);
+path.lineTo(374.00174,154.571106);
+path.close();
+path.moveTo(378.917297, 141.233871);
+path.quadTo(370.233887,146.511475, 364.390686,157.898193);
+path.lineTo(374.00174,154.571106);
+path.lineTo(354.009216,208.816208);
+path.lineTo(398.164948,136.674606);
+path.quadTo(388.299255,136.674606, 380.294495,140.44487);
+</div>
+
+<div id="testQuadratic47o">
+path.moveTo(343.939362, 212.598053);
+path.lineTo(378.457642, 118.940636);
+path.lineTo(383.692657, 141.516571);
+path.lineTo(350.319519, 231.902115);
+path.lineTo(343.939362, 212.598053);
+path.close();
+path.moveTo(325.429016, 162.047577);
+path.quadTo(336.348907, 149.123688, 353.36264, 149.123688);
+path.quadTo(369.476624, 149.123688, 378.269806, 160.575241);
+path.lineTo(325.429016, 162.047577);
+path.close();
+path.moveTo(370.867188, 186.014069);
+path.quadTo(370.867188, 161.229614, 352.381104, 161.229614);
+path.quadTo(333.813202, 161.229614, 331.686493, 186.014069);
+path.lineTo(370.867188, 186.014069);
+path.close();
+path.moveTo(353.161499, 195.011719);
+path.quadTo(353.161499, 174.726105, 363.876862, 161.96579);
+path.lineTo(353.161499, 195.011719);
+path.close();
+</div>
+
+<div id="testQuadratic47s">
+path.moveTo(366.466309, 151.476364);
+path.lineTo(378.457642,118.940636);
+path.lineTo(383.692657,141.516571);
+path.lineTo(377.159943,159.209305);
+path.quadTo(377.728729,159.87059, 378.269806,160.575241);
+path.lineTo(376.638824,160.620682);
+path.lineTo(370.26593,177.8806);
+path.quadTo(368.708496,168.390671, 363.116943,164.309357);
+path.lineTo(356.079041,186.014069);
+path.lineTo(367.262817,186.014069);
+path.lineTo(350.319519,231.902115);
+path.lineTo(343.939362,212.598053);
+path.lineTo(353.736816,186.014923);
+path.lineTo(353.737122,186.014069);
+path.lineTo(353.736938,186.014069);
+path.quadTo(353.736877,186.014496, 353.736816,186.014923);
+path.quadTo(353.161499,190.31131, 353.161499,195.011719);
+</div>
+
+<div id="testQuadratic48o">
+path.moveTo(366.608826, 151.196014);
+path.quadTo(378.803101, 136.674606, 398.164948, 136.674606);
+path.lineTo(354.009216, 208.816208);
+path.lineTo(393.291473, 102.232819);
+path.lineTo(359.978058, 136.581512);
+path.quadTo(378.315979, 136.581512, 388.322723, 149.613556);
+path.lineTo(364.390686, 157.898193);
+path.quadTo(375.281769, 136.674606, 396.039917, 136.674606);
+path.lineTo(350, 120);
+path.lineTo(366.608826, 151.196014);
+path.close();
+</div>
+
+<div id="testQuadratic48s">
+path.moveTo(369.285553, 126.984779);
+path.lineTo(393.291473,102.232819);
+path.lineTo(382.416199,131.740402);
+path.lineTo(396.039917,136.674606);
+path.quadTo(387.290802,136.674606, 380.294495,140.44487);
+path.quadTo(379.623352,140.760971, 378.965302,141.103577);
+path.lineTo(378.917297,141.233856);
+path.quadTo(378.86972,141.206131, 378.822021,141.178574);
+path.quadTo(372.011871,144.761871, 366.608826,151.196014);
+path.lineTo(350,120);
+path.lineTo(369.285553,126.984779);
+path.close();
+</div>
+
+<div id="testQuadratic49s">
+path.moveTo(366.400513, 204.162521);
+path.lineTo(411.545044, 81.6732483);
+path.lineTo(366.400513, 204.162521);
+path.close();
+path.moveTo(331.585693, 138.050415);
+path.quadTo(345.867188, 121.147957, 368.11853, 121.147957);
+path.quadTo(389.193115, 121.147957, 400.693176, 136.124817);
+path.lineTo(331.585693, 138.050415);
+path.close();
+path.moveTo(369.863983, 145.645813);
+path.quadTo(382.380371, 121.254936, 406.236359, 121.254936);
+path.lineTo(369.863983, 145.645813);
+path.close();
+path.moveTo(369.970581, 137.94342);
+path.quadTo(383.98465, 121.254936, 406.235992, 121.254936);
+path.lineTo(369.970581, 137.94342);
+path.close();
+</div>
+
+<div id="testQuadratic50o">
+path.moveTo(366.400513, 204.162521);
+path.lineTo(411.545044, 81.6732483);
+path.lineTo(366.400513, 204.162521);
+path.close();
+path.moveTo(331.585693, 138.050415);
+path.quadTo(345.867188, 121.147957, 368.11853, 121.147957);
+path.quadTo(389.193115, 121.147957, 400.693176, 136.124817);
+path.lineTo(331.585693, 138.050415);
+path.close();
+path.moveTo(369.863983, 145.645813);
+path.quadTo(382.380371, 121.254936, 406.236359, 121.254936);
+path.lineTo(369.863983, 145.645813);
+path.close();
+path.moveTo(369.970581, 137.94342);
+path.quadTo(383.98465, 121.254936, 406.235992, 121.254936);
+path.lineTo(369.970581, 137.94342);
+path.close();
+</div>
+
+<div id="testQuadratic50s">
+path.moveTo(331.585693, 138.050415);
+path.quadTo(345.867188,121.147957, 368.11853,121.147957);
+path.quadTo(378.797424,121.147957, 387.017914,124.993469);
+path.quadTo(391.577667,123.030998, 396.645874,122.098694);
+path.quadTo(401.232697,121.254936, 406.235992,121.254936);
+path.lineTo(395.061676,126.397095);
+path.lineTo(391.979187,127.81559);
+path.quadTo(393.010406,128.517273, 393.994415,129.292801);
+path.quadTo(394.053131,129.339066, 394.111664,129.385605);
+path.lineTo(393.910492,129.520508);
+path.lineTo(383.340973,136.608322);
+path.lineTo(375.350006,136.830978);
+path.quadTo(376.20224,135.708145, 377.092102,134.66626);
+path.lineTo(372.197113,136.918823);
+</div>
+
+<div id="testQuadratic51">
+path.moveTo(369.863983, 145.645813);
+path.quadTo(382.380371, 121.254936, 406.236359, 121.254936);
+path.lineTo(369.863983, 145.645813);
+path.close();
+path.moveTo(369.970581, 137.94342);
+path.quadTo(383.98465, 121.254936, 406.235992, 121.254936);
+path.lineTo(369.970581, 137.94342);
+path.close();
+</div>
+
+<div id="testQuadratic52o">
+path.moveTo(366.400513, 204.162521);
+path.lineTo(411.545044, 81.6732483);
+path.lineTo(366.400513, 204.162521);
+path.close();
+path.moveTo(331.585693, 138.050415);
+path.quadTo(345.867188, 121.147957, 368.11853, 121.147957);
+path.quadTo(389.193115, 121.147957, 400.693176, 136.124817);
+path.lineTo(331.585693, 138.050415);
+path.close();
+path.moveTo(369.863983, 145.645813);
+path.quadTo(382.380371, 121.254936, 406.236359, 121.254936);
+path.lineTo(369.863983, 145.645813);
+path.close();
+path.moveTo(369.970581, 137.94342);
+path.quadTo(383.98465, 121.254936, 406.235992, 121.254936);
+path.lineTo(369.970581, 137.94342);
+path.close();
+</div>
+
+<div id="testQuadratic52s">
+path.moveTo(331.585693, 138.050415);
+path.quadTo(345.867188,121.147957, 368.11853,121.147957);
+path.quadTo(378.797424,121.147957, 387.017914,124.993469);
+path.quadTo(391.577667,123.030998, 396.645874,122.098694);
+path.quadTo(401.232697,121.254936, 406.235992,121.254936);
+path.close();
+path.moveTo(383.340973, 136.608322);
+path.lineTo(369.863983,145.645813);
+path.quadTo(372.378204,140.746292, 375.350006,136.830978);
+path.lineTo(372.197113,136.918823);
+path.lineTo(369.970581,137.94342);
+path.quadTo(370.390961,137.442825, 370.818756,136.95723);
+path.lineTo(331.585693,138.050415);
+path.quadTo(345.867188,121.147957, 368.11853,121.147957);
+path.quadTo(378.797424,121.147957, 387.017914,124.993469);
+path.quadTo(391.577667,123.030998, 396.645874,122.098694);
+path.quadTo(401.232697,121.254936, 406.235992,121.254936);
+path.close();
+path.moveTo(383.340973, 136.608322);
+path.lineTo(391.380798,136.384293);
+path.lineTo(400.693176,136.124817);
+path.quadTo(397.721985,132.255341, 394.111664,129.385605);
+path.lineTo(406.236359,121.254936);
+path.quadTo(406.236176,121.254936, 406.235992,121.254936);
+path.lineTo(406.235992,121.254936);
+path.quadTo(401.232697,121.254936, 396.645874,122.098694);
+path.quadTo(391.577667,123.030998, 387.017914,124.993469);
+path.quadTo(378.797424,121.147957, 368.11853,121.147957);
+path.quadTo(345.867188,121.147957, 331.585693,138.050415);
+path.lineTo(370.818756,136.95723);
+path.quadTo(370.390961,137.442825, 369.970581,137.94342);
+path.lineTo(372.197113,136.918823);
+path.lineTo(375.350006,136.830978);
+path.quadTo(372.378204,140.746292, 369.863983,145.645813);
+path.lineTo(383.340973,136.608322);
+path.close();
+</div>
+
+<div id="testQuadratic52sa">
+path.moveTo(331.585693, 138.050415);
+path.quadTo(345.867188,121.147957, 368.11853,121.147957);
+path.quadTo(378.797424,121.147957, 387.017914,124.993469);
+path.quadTo(391.577667,123.030998, 396.645874,122.098694);
+path.quadTo(401.232697,121.254936, 406.235992,121.254936);
+path.close();
+</div>
+
+<div id="testQuadratic52sb">
+path.moveTo(383.340973, 136.608322);
+path.lineTo(369.863983,145.645813);
+path.quadTo(372.378204,140.746292, 375.350006,136.830978);
+path.lineTo(372.197113,136.918823);
+path.lineTo(369.970581,137.94342);
+path.quadTo(370.390961,137.442825, 370.818756,136.95723);
+path.lineTo(331.585693,138.050415);
+path.quadTo(345.867188,121.147957, 368.11853,121.147957);
+path.quadTo(378.797424,121.147957, 387.017914,124.993469);
+path.quadTo(391.577667,123.030998, 396.645874,122.098694);
+path.quadTo(401.232697,121.254936, 406.235992,121.254936);
+path.close();
+</div>
+
+<div id="testQuadratic52sc">
+path.moveTo(383.340973, 136.608322);
+path.lineTo(391.380798,136.384293);
+path.lineTo(400.693176,136.124817);
+path.quadTo(397.721985,132.255341, 394.111664,129.385605);
+path.lineTo(406.236359,121.254936);
+path.quadTo(406.236176,121.254936, 406.235992,121.254936);
+path.lineTo(406.235992,121.254936);
+path.quadTo(401.232697,121.254936, 396.645874,122.098694);
+path.quadTo(391.577667,123.030998, 387.017914,124.993469);
+path.quadTo(378.797424,121.147957, 368.11853,121.147957);
+path.quadTo(345.867188,121.147957, 331.585693,138.050415);
+path.lineTo(370.818756,136.95723);
+path.quadTo(370.390961,137.442825, 369.970581,137.94342);
+path.lineTo(372.197113,136.918823);
+path.lineTo(375.350006,136.830978);
+path.quadTo(372.378204,140.746292, 369.863983,145.645813);
+path.lineTo(383.340973,136.608322);
+path.close();
+</div>
+
+<div id="testQuadratic53o">
+path.moveTo(303.12088, 141.299606);
+path.lineTo(330.463562, 217.659027);
+path.lineTo(303.12088, 141.299606);
+path.close();
+path.moveTo(371.919067, 205.854996);
+path.lineTo(326.236786, 205.854996);
+path.quadTo(329.104431, 231.663818, 351.512085, 231.663818);
+path.lineTo(371.919067, 205.854996);
+path.close();
+</div>
+
+<div id="testQuadratic53s">
+path.moveTo(326.236786,205.854996);
+path.lineTo(326.236786,205.854996);
+path.close();
+path.moveTo(371.919067,205.854996);
+path.lineTo(326.236786,205.854996);
+</div>
+
+<div id="testQuadratic54">
+path.moveTo(303.12088, 141.299606);
+path.lineTo(330.463562, 217.659027);
+path.lineTo(358.606506, 141.299606);
+path.lineTo(303.12088, 141.299606);
+path.close();
+path.moveTo(371.919067, 205.854996);
+path.lineTo(326.236786, 205.854996);
+path.quadTo(329.104431, 231.663818, 351.512085, 231.663818);
+path.lineTo(371.919067, 205.854996);
+path.close();
+</div>
+
+<div id="testQuadratic55o">
+path.moveTo(303.12088, 141.299606);
+path.lineTo(330.463562, 217.659027);
+path.lineTo(358.606506, 141.299606);
+path.lineTo(303.12088, 141.299606);
+path.close();
+path.moveTo(326.236786, 205.854996);
+path.quadTo(329.104431, 231.663818, 351.512085, 231.663818);
+path.lineTo(326.236786, 205.854996);
+path.close();
+</div>
+
+<div id="testQuadratic55s">
+path.moveTo(326.236786,205.854996);
+path.lineTo(303.12088,141.299606);
+path.lineTo(358.606506,141.299606);
+path.lineTo(332.468719,212.218475);
+path.lineTo(351.512085,231.663818);
+path.quadTo(329.104431,231.663818, 326.236786,205.854996);
+path.close();
+</div>
+
+<div id="testQuadratic56o">
+path.moveTo(366.608826, 151.196014);
+path.quadTo(378.803101, 136.674606, 398.164948, 136.674606);
+path.lineTo(354.009216, 208.816208);
+path.lineTo(393.291473, 102.232819);
+path.lineTo(359.978058, 136.581512);
+path.quadTo(378.315979, 136.581512, 388.322723, 149.613556);
+path.lineTo(364.390686, 157.898193);
+path.quadTo(375.281769, 136.674606, 396.039917, 136.674606);
+path.lineTo(350, 120);
+path.lineTo(366.608826, 151.196014);
+path.close();
+</div>
+
+<div id="testQuadratic56s">
+path.moveTo(369.285553,126.984779);
+path.lineTo(393.291473,102.232819);
+path.lineTo(382.416199,131.740402);
+path.lineTo(396.039917,136.674606);
+path.quadTo(387.290802,136.674606, 380.294495,140.44487);
+path.quadTo(379.623352,140.760971, 378.965302,141.103577);
+path.lineTo(378.917297,141.233856);
+path.quadTo(378.86972,141.206131, 378.822021,141.178574);
+path.quadTo(372.011871,144.761871, 366.608826,151.196014);
+path.lineTo(350,120);
+path.lineTo(369.285553,126.984779);
+path.close();
+path.moveTo(378.917297,141.233871);
+path.lineTo(378.917297,141.233856);
+path.quadTo(378.86972,141.206131, 378.822021,141.178574);
+path.quadTo(372.011871,144.761871, 366.608826,151.196014);
+</div>
+
+<div id="testQuadratic57o">
+path.moveTo(303.12088, 141.299606);
+path.lineTo(330.463562, 217.659027);
+path.lineTo(358.606506, 141.299606);
+path.lineTo(362.874634, 159.705902);
+path.lineTo(335.665344, 233.397751);
+path.lineTo(322.12738, 233.397751);
+path.lineTo(295.718353, 159.505829);
+path.lineTo(295.718353, 240);
+path.lineTo(303.12088, 141.299606);
+path.close();
+path.moveTo(322.935669, 231.030273);
+path.quadTo(312.832214, 220.393295, 312.832214, 203.454178);
+path.quadTo(312.832214, 186.981888, 321.73526, 176.444946);
+path.quadTo(330.638306, 165.90802, 344.509705, 165.90802);
+path.lineTo(371.919067, 205.854996);
+path.lineTo(326.236786, 205.854996);
+path.quadTo(329.104431, 231.663818, 351.512085, 231.663818);
+path.lineTo(322.935669, 231.030273);
+path.close();
+path.moveTo(326.837006, 195.984955);
+path.lineTo(358.78125, 195.984955);
+path.lineTo(343.709442, 175.778046);
+path.quadTo(328.570923, 175.778046, 326.837006, 195.984955);
+path.close();
+</div>
+
+<div id="testQuadratic57s">
+path.moveTo(300.708282,173.46756);
+path.lineTo(303.12088,141.299606);
+path.lineTo(317.770294,182.210785);
+path.quadTo(319.462738,179.134506, 321.73526,176.444946);
+path.quadTo(330.638306,165.90802, 344.509705,165.90802);
+path.lineTo(347.780151,170.674438);
+path.lineTo(358.606506,141.299606);
+path.lineTo(362.874634,159.705902);
+path.lineTo(354.960693,181.139511);
+path.lineTo(371.919067,205.854996);
+path.lineTo(345.834961,205.854996);
+path.lineTo(337.609253,228.13298);
+path.quadTo(342.649323,231.302383, 349.843323,231.626816);
+path.lineTo(336.429047,231.329422);
+path.lineTo(335.665344,233.397751);
+path.lineTo(322.12738,233.397751);
+path.lineTo(320.050781,227.587433);
+path.quadTo(313.982483,219.336182, 313.015503,207.902908);
+path.lineTo(300.708282,173.46756);
+path.close();
+path.moveTo(300.708282,173.46756);
+path.lineTo(295.718353,240);
+path.lineTo(295.718353,159.505829);
+path.lineTo(300.708282,173.46756);
+path.close();
+path.moveTo(349.843323,231.626816);
+path.lineTo(351.512085,231.663818);
+path.quadTo(350.663696,231.663818, 349.843323,231.626816);
+path.close();
+path.moveTo(326.236786,205.854996);
+path.lineTo(330.463562,217.659027);
+path.lineTo(334.814056,205.854996);
+path.lineTo(326.236786,205.854996);
+path.close();
+path.moveTo(334.814056,205.854996);
+path.lineTo(338.451721,195.984955);
+path.lineTo(349.479309,195.984955);
+path.lineTo(352.559326,187.643173);
+path.lineTo(358.78125,195.984955);
+</div>
+
+<div id="testQuadratic58o">
+path.moveTo(283.714233, 240);
+path.lineTo(283.714233, 141.299606);
+path.lineTo(303.12088, 141.299606);
+path.lineTo(330.463562, 217.659027);
+path.lineTo(358.606506, 141.299606);
+path.lineTo(362.874634, 159.705902);
+path.lineTo(335.665344, 233.397751);
+path.lineTo(322.12738, 233.397751);
+path.lineTo(295.718353, 159.505829);
+path.lineTo(295.718353, 240);
+path.lineTo(283.714233, 240);
+path.close();
+path.moveTo(322.935669, 231.030273);
+path.quadTo(312.832214, 220.393295, 312.832214, 203.454178);
+path.quadTo(312.832214, 186.981888, 321.73526, 176.444946);
+path.quadTo(330.638306, 165.90802, 344.509705, 165.90802);
+path.quadTo(357.647522, 165.90802, 364.81665, 175.244537);
+path.lineTo(371.919067, 205.854996);
+path.lineTo(326.236786, 205.854996);
+path.quadTo(329.104431, 231.663818, 351.512085, 231.663818);
+path.lineTo(322.935669, 231.030273);
+path.close();
+path.moveTo(326.837006, 195.984955);
+path.lineTo(358.78125, 195.984955);
+path.quadTo(358.78125, 175.778046, 343.709442, 175.778046);
+path.quadTo(328.570923, 175.778046, 326.837006, 195.984955);
+path.close();
+</div>
+
+<div id="testQuadratic58s">
+path.moveTo(283.714233,240);
+path.lineTo(283.714233,141.299606);
+path.lineTo(303.12088,141.299606);
+path.lineTo(317.770294,182.210785);
+path.quadTo(319.462738,179.134506, 321.73526,176.444946);
+path.quadTo(330.638306,165.90802, 344.509705,165.90802);
+path.quadTo(347.07132,165.90802, 349.406036,166.26297);
+path.lineTo(358.606506,141.299606);
+path.lineTo(362.874634,159.705902);
+path.lineTo(359.116211,169.884979);
+path.quadTo(362.326477,172.001541, 364.81665,175.244537);
+path.lineTo(371.919067,205.854996);
+path.lineTo(345.834961,205.854996);
+path.lineTo(337.609253,228.13298);
+path.quadTo(342.649323,231.302383, 349.843323,231.626816);
+path.lineTo(336.429047,231.329422);
+path.lineTo(335.665344,233.397751);
+path.lineTo(322.12738,233.397751);
+path.lineTo(320.050781,227.587433);
+path.quadTo(313.982483,219.336182, 313.015503,207.902908);
+path.lineTo(295.718353,159.505829);
+path.lineTo(295.718353,240);
+path.lineTo(283.714233,240);
+path.close();
+path.moveTo(349.843323,231.626816);
+path.lineTo(351.512085,231.663818);
+path.quadTo(350.663696,231.663818, 349.843323,231.626816);
+path.close();
+path.moveTo(326.236786,205.854996);
+path.lineTo(330.463562,217.659027);
+path.lineTo(334.814056,205.854996);
+path.lineTo(326.236786,205.854996);
+path.close();
+path.moveTo(334.814056,205.854996);
+path.lineTo(338.451721,195.984955);
+path.lineTo(349.479309,195.984955);
+path.lineTo(355.054535,180.885361);
+path.quadTo(358.78125,185.936935, 358.78125,195.984955);
+</div>
+
+<div id="testQuadratic58a">
+path.moveTo(283.714233,240);
+path.lineTo(283.714233,141.299606);
+path.lineTo(303.12088,141.299606);
+path.lineTo(317.770294,182.210785);
+path.quadTo(319.462738,179.134506, 321.73526,176.444946);
+path.quadTo(330.638306,165.90802, 344.509705,165.90802);
+path.quadTo(347.07132,165.90802, 349.406036,166.26297);
+path.lineTo(358.606506,141.299606);
+path.lineTo(362.874634,159.705902);
+path.lineTo(359.116211,169.884979);
+path.quadTo(362.326477,172.001541, 364.81665,175.244537);
+path.lineTo(371.919067,205.854996);
+path.lineTo(345.834961,205.854996);
+path.lineTo(337.609253,228.13298);
+path.quadTo(342.649323,231.302383, 349.843323,231.626816);
+path.lineTo(336.429047,231.329422);
+path.lineTo(335.665344,233.397751);
+path.lineTo(322.12738,233.397751);
+path.lineTo(320.050781,227.587433);
+path.quadTo(313.982483,219.336182, 313.015503,207.902908);
+path.lineTo(295.718353,159.505829);
+path.lineTo(295.718353,240);
+path.lineTo(283.714233,240);
+path.close();
+path.moveTo(349.843323,231.626816);
+path.lineTo(351.512085,231.663818);
+path.quadTo(350.663696,231.663818, 349.843323,231.626816);
+path.close();
+path.moveTo(349.479309,195.984955);
+path.lineTo(358.78125,195.984955);
+path.quadTo(358.78125,185.936935, 355.054535,180.885361);
+path.lineTo(349.479309,195.984955);
+path.close();
+path.moveTo(345.858368,175.888794);
+path.lineTo(338.451721,195.984955);
+path.lineTo(326.837006,195.984955);
+path.quadTo(328.570923,175.778046, 343.709442,175.778046);
+path.quadTo(344.825195,175.778046, 345.858368,175.888794);
+path.close();
+</div>
+
+<div id="testQuadratic59o">
+path.moveTo(369.863983, 145.645813);
+path.quadTo(382.380371, 121.254936, 406.236359, 121.254936);
+path.quadTo(409.445679, 121.254936, 412.975952, 121.789818);
+path.lineTo(369.863983, 145.645813);
+path.close();
+path.moveTo(369.970581, 137.94342);
+path.quadTo(383.98465, 121.254936, 406.235992, 121.254936);
+path.quadTo(425.705902, 121.254936, 439.71994, 137.087616);
+path.lineTo(369.970581, 137.94342);
+path.close();
+</div>
+
+<div id="testQuadratic59s">
+path.moveTo(369.970581,137.94342);
+path.quadTo(383.98465,121.254936, 406.235992,121.254936);
+path.quadTo(406.237854,121.254936, 406.239746,121.254936);
+path.lineTo(406.239532,121.254936);
+path.quadTo(409.447418,121.255203, 412.975952,121.789818);
+</div>
+
+<div id="testQuadratic60">
+path.moveTo(360.416077, 166.795715);
+path.quadTo(370.126831, 147.872162, 388.635406, 147.872162);
+path.lineTo(360.416077, 166.795715);
+path.close();
+path.moveTo(353.2948, 194.351074);
+path.quadTo(353.2948, 173.767563, 364.167572, 160.819855);
+path.quadTo(375.040314, 147.872162, 392.303894, 147.872162);
+path.lineTo(353.2948, 194.351074);
+path.close();
+</div>
+
+<div id="testQuadratic61">
+path.moveTo(348.781738, 123.864815);
+path.lineTo(369.848602, 123.864815);
+path.lineTo(369.848602, 145.680267);
+path.quadTo(382.360413, 121.298294, 406.207703, 121.298294);
+path.lineTo(348.781738, 123.864815);
+path.close();
+path.moveTo(369.961151, 137.980698);
+path.quadTo(383.970093, 121.298294, 406.213287, 121.298294);
+path.lineTo(369.961151, 137.980698);
+path.close();
+</div>
+
+<div id="testQuadratic62x">
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.lineTo(2, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(2, 0);
+ path.quadTo(3, 1, 1, 2);
+ path.close();
+</div>
+
+<div id="testLine1a">
+ path.addRect(0, 0, 12, 12, SkPath::kCW_Direction);
+ path.addRect(4, 0, 13, 13, SkPath::kCCW_Direction);
+ path.close();
+</div>
+
+<div id="testQuadratic63">
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.lineTo(3, 2);
+ path.close();
+ path.moveTo(1, 0);
+ path.lineTo(2, 1);
+ path.quadTo(2, 1, 2, 2);
+ path.close();
+</div>
+
+<div id="testQuadratic64">
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.lineTo(2, 3);
+ path.close();
+ path.moveTo(1, 2);
+ path.lineTo(2, 2);
+ path.quadTo(0, 3, 3, 3);
+ path.close();
+</div>
+
+<div id="testQuadratic65">
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 0);
+ path.lineTo(3, 2);
+ path.close();
+ path.moveTo(2, 1);
+ path.lineTo(2, 2);
+ path.quadTo(0, 3, 1, 3);
+ path.close();
+</div>
+
+<div id="testQuadratic66">
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 1);
+ path.lineTo(3, 2);
+ path.close();
+ path.moveTo(2, 0);
+ path.lineTo(1, 1);
+ path.quadTo(3, 2, 2, 3);
+ path.close();
+</div>
+
+<div id="testQuadratic67x">
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 2, 1);
+ path.lineTo(2, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(2, 0);
+ path.quadTo(1, 1, 3, 2);
+ path.close();
+</div>
+
+<div id="testQuadratic68">
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.lineTo(1, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(0, 1, 2, 1);
+ path.close();
+</div>
+
+<div id="testQuadratic69">
+ path.moveTo(0, 0);
+ path.quadTo(0, 0, 0, 1);
+ path.lineTo(3, 2);
+ path.close();
+ path.moveTo(2, 0);
+ path.lineTo(1, 1);
+ path.quadTo(3, 2, 2, 3);
+ path.close();
+</div>
+
+<div id="testQuadratic70x">
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 0, 1);
+ path.lineTo(1, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(0, 1, 2, 1);
+ path.close();
+</div>
+
+<div id="testQuadratic71">
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 1, 1);
+ path.lineTo(3, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.quadTo(1, 1, 3, 1);
+ path.close();
+</div>
+
+<div id="testQuadratic72">
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 1, 2);
+ path.lineTo(1, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.quadTo(0, 1, 3, 2);
+ path.close();
+</div>
+
+<div id="testQuadratic73">
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 0, 3);
+ path.lineTo(0, 3);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.quadTo(0, 1, 1, 1);
+ path.close();
+</div>
+
+<div id="testQuadratic74">
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 1, 3);
+ path.lineTo(1, 3);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 1);
+ path.quadTo(3, 2, 2, 3);
+ path.close();
+</div>
+
+<div id="testQuadratic75">
+ path.moveTo(0, 0);
+ path.quadTo(1, 0, 1, 3);
+ path.lineTo(2, 3);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 1);
+ path.quadTo(3, 2, 2, 3);
+ path.close();
+</div>
+
+</div>
+
+<script type="text/javascript">
+
+var testDivs = [
+ testQuadratic75,
+ testQuadratic74,
+ testQuadratic73,
+ testQuadratic72,
+ testQuadratic71,
+ testQuadratic70x,
+ testQuadratic69,
+ testQuadratic68,
+ testQuadratic67x,
+ testQuadratic66,
+ testQuadratic65,
+ testQuadratic64,
+ testQuadratic63,
+ testLine1a,
+ testQuadratic62x,
+ testLine81,
+ testQuadratic61,
+ testQuadratic60,
+ testQuadratic59o,
+ testQuadratic59s,
+ testQuadratic58o,
+ testQuadratic58a,
+ testQuadratic58s,
+ testQuadratic57o,
+ testQuadratic57s,
+ testQuadratic56o,
+ testQuadratic56s,
+ testQuadratic55o,
+ testQuadratic55s,
+ testQuadratic54,
+ testQuadratic53o,
+ testQuadratic53s,
+ testQuadratic52sa,
+ testQuadratic52sb,
+ testQuadratic52sc,
+ testQuadratic52o,
+ testQuadratic52s,
+ testQuadratic51,
+ testQuadratic50o,
+ testQuadratic50s,
+ testQuadratic49s,
+ testQuadratic48o,
+ testQuadratic48s,
+ testQuadratic47o,
+ testQuadratic47s,
+ testQuadratic46o,
+ testQuadratic46s,
+ testQuadratic45o,
+ testQuadratic45s,
+ testQuadratic44o,
+ testQuadratic44s,
+ testQuadratic43o,
+ testQuadratic43s,
+ testQuadratic42o,
+ testQuadratic42s,
+ testQuadratic41o,
+ testQuadratic41s,
+ testQuadratic40xb,
+ testQuadratic40xa,
+ testQuadratic40x,
+ testQuadratic39,
+ testQuadratic39a,
+ testQuadratic38,
+ testQuadratic37,
+ testQuadratic36,
+ testQuadratic35,
+ testQuadratic34,
+ testQuadratic33,
+ testQuadratic32,
+ testQuadratic31,
+ testQuadratic30,
+ testQuadratic29,
+ testQuadratic28,
+ testQuadratic27,
+ testQuadratic26,
+ testQuadratic25,
+ testQuadratic24,
+ testQuadratic23,
+ testQuadratic22,
+ testQuadratic21,
+ testQuadratic20,
+ testQuadratic19,
+ testQuadratic18,
+ testQuadratic17x,
+ testQuadratic16b,
+ testQuadratic16a,
+ testQuadratic15,
+ testQuadratic14,
+ testQuadratic13b,
+ testQuadratic13a,
+ testQuadratic12,
+ testQuadratic11b,
+ testQuadratic11a,
+ testQuadratic10b,
+ testQuadratic10a,
+ testQuadratic9a,
+ testQuadratic9,
+ testQuadratic8,
+ testQuadratic7,
+ testQuadratic6,
+ testQuadratic5,
+ testQuadratic4x,
+ testQuadratic3,
+ testQuadratic2,
+ testQuadratic1,
+ testLine4x,
+ testLine3x,
+ testLine2x,
+ testLine1x,
+ testQuadralateral9,
+ testQuadralateral8,
+ testQuadralateral7,
+ testFauxQuadralateral6d,
+ testFauxQuadralateral6c,
+ testFauxQuadralateral6b,
+ testFauxQuadralateral6a,
+ testFauxQuadralateral6,
+ testQuadralateral6a,
+ testQuadralateral6,
+ testQuadralateral5,
+ testNondegenerate4,
+ testNondegenerate3,
+ testNondegenerate2,
+ testNondegenerate1,
+ testDegenerate4,
+ testDegenerate3,
+ testDegenerate2,
+ testDegenerate1,
+ testLine79,
+ testLine78,
+ testLine77,
+ testLine76,
+ testLine75,
+ testLine74,
+ testLine73,
+ testLine72,
+ testLine71,
+ testLine70,
+ testLine69,
+ testLine68f,
+ testLine68e,
+ testLine68d,
+ testLine68c,
+ testLine68b,
+ testLine68a,
+ testLine67,
+ testLine66,
+ testLine65,
+ testLine64,
+ testLine63,
+ testLine62,
+ testLine61,
+ testLine60,
+ testLine59,
+ testLine58,
+ testLine57,
+ testLine56,
+ testLine55,
+ testLine54,
+ testLine53,
+ testLine52,
+ testLine51,
+ testLine50,
+ testLine49,
+ testLine48,
+ testLine47,
+ testLine46,
+ testLine45,
+ testLine44,
+ testLine43,
+ testLine42,
+ testLine41,
+ testLine40,
+ testLine39,
+ testLine38,
+ testLine37,
+ testLine36,
+ testLine35,
+ testLine34,
+ testLine33,
+ testLine32,
+ testLine31,
+ testLine30,
+ testLine29,
+ testLine28,
+ testLine24,
+ testLine22,
+ testLine19,
+ testLine17,
+ testLine13,
+ testLine12,
+ testLine9,
+ testLine7b,
+ testLine7,
+ testSimplifyQuadratic21,
+ testSimplifyQuadratic20,
+ testSimplifyQuadratic19,
+ testSimplifyQuadratic18,
+ testSimplifyQuadratic17,
+ testSimplifyQuadratic16,
+ testSimplifyQuadratic15,
+ testSimplifyQuadratic14,
+ testSimplifyQuadratic13,
+ testSimplifyQuadratic12,
+ testSimplifyQuadratic11,
+ testSimplifyQuadratic10,
+ testSimplifyQuadratic9,
+ testSimplifyQuadratic8,
+ testSimplifyQuadratic7,
+ testSimplifyQuadratic6,
+ testSimplifyQuadratic5,
+ testSimplifyQuadratic4,
+ testSimplifyQuadratic3,
+ testSimplifyQuadratic2,
+ testSimplifyQuadratic1,
+];
+
+var scale, columns, rows, xStart, yStart;
+
+var ticks = 0.1;
+var at_x = 13 + 0.5;
+var at_y = 13 + 0.5;
+var decimal_places = 0; // make this 3 to show more precision
+
+var tests = [];
+var testTitles = [];
+var testIndex = 0;
+var hasXor = false;
+var draw_labels = true;
+
+var ctx;
+
+function parse(test, title) {
+ var contours = [];
+ var contourStrs = test.split("path.close();");
+ var pattern = /-?\d+\.*\d*/g;
+ hasXor = test.split("kEvenOdd_FillType").length > 1;
+ for (var c in contourStrs) {
+ var contour = contourStrs[c];
+ var verbStrs = contour.split("path");
+ var verbs = [];
+ for (var v in verbStrs) {
+ var verbStr = verbStrs[v];
+ var points = verbStr.match(pattern);
+ var pts = [];
+ for (var wd in points) {
+ var num = parseFloat(points[wd]);
+ if (isNaN(num)) continue;
+ pts.push(num);
+ }
+ if (pts.length > 0)
+ verbs.push(pts);
+ }
+ if (verbs.length > 0) {
+ var lastIndex = verbs.length - 1;
+ var lastVerb = verbs[lastIndex];
+ var lastLen = lastVerb.length;
+ if (verbs[0][0] != lastVerb[lastLen - 2] && verbs[0][1] != lastVerb[lastLen - 1]) {
+ var lastPts = [];
+ lastPts.push(verbs[0][0]);
+ lastPts.push(verbs[0][1]);
+ verbs.push(lastPts);
+ }
+ contours.push(verbs);
+ }
+ }
+ if (contours.length > 0) {
+ tests.push(contours);
+ testTitles.push(title);
+ }
+}
+
+function parseRect(test, title) {
+ var contours = [];
+ var rectStrs = test.split("path.addRect");
+ var pattern = /-?\d+\.*\d*/g;
+ hasXor = test.split("kEvenOdd_FillType").length > 1;
+ for (var r in rectStrs) {
+ var rect = rectStrs[r];
+ var sideStrs = rect.match(pattern);
+ var ccw = rect.split("kCCW_Direction").length > 1;
+ var sides = [];
+ for (var wd in sideStrs) {
+ var num = parseFloat(sideStrs[wd]);
+ if (isNaN(num)) continue;
+ sides.push(num);
+ }
+ if (sides.length == 0)
+ continue;
+ var verbs = [];
+ var topLeft = [];
+ topLeft.push(sides[0]); topLeft.push(sides[1]);
+ var topRight = [];
+ topRight.push(sides[2]); topRight.push(sides[1]);
+ var botLeft = [];
+ botLeft.push(sides[0]); botLeft.push(sides[3]);
+ var botRight = [];
+ botRight.push(sides[2]); botRight.push(sides[3]);
+ verbs.push(topLeft);
+ if (!ccw) {
+ verbs.push(topRight);
+ verbs.push(botRight);
+ verbs.push(botLeft);
+ } else {
+ verbs.push(botLeft);
+ verbs.push(botRight);
+ verbs.push(topRight);
+ }
+ verbs.push(topLeft);
+ contours.push(verbs);
+ }
+ if (contours.length > 0) {
+ tests.push(contours);
+ testTitles.push(title);
+ }
+}
+
+function init(test) {
+ var canvas = document.getElementById('canvas');
+ if (!canvas.getContext) return;
+ canvas.width = window.innerWidth - at_x;
+ canvas.height = window.innerHeight - at_y;
+ ctx = canvas.getContext('2d');
+ var xmin = Infinity;
+ var xmax = -Infinity;
+ var ymin = Infinity;
+ var ymax = -Infinity;
+ for (var contours in test) {
+ var contour = test[contours];
+ for (var verbs in contour) {
+ var verb = contour[verbs];
+ var last = verb.length;
+ for (var idx = 0; idx < last; idx += 2) {
+ xmin = Math.min(xmin, verb[idx]);
+ xmax = Math.max(xmax, verb[idx]);
+ ymin = Math.min(ymin, verb[idx + 1]);
+ ymax = Math.max(ymax, verb[idx + 1]);
+ }
+ }
+ }
+ var subscale = 1;
+ while ((xmax - xmin) * subscale < 0.1 && (ymax - ymin) * subscale < 0.1) {
+ subscale *= 10;
+ }
+ columns = Math.ceil(xmax) - Math.floor(xmin) + 1;
+ rows = Math.ceil(ymax) - Math.floor(ymin) + 1;
+ xStart = Math.floor(xmin);
+ yStart = Math.floor(ymin);
+ var hscale = ctx.canvas.width / columns / ticks;
+ var vscale = ctx.canvas.height / rows / ticks;
+ scale = Math.floor(Math.min(hscale, vscale)) * subscale;
+}
+
+function drawPoint(px, py, xoffset, yoffset, unit) {
+ var label = px.toFixed(decimal_places) + ", " + py.toFixed(decimal_places);
+ var _px = px * unit + xoffset;
+ var _py = py * unit + yoffset;
+ ctx.beginPath();
+ ctx.arc(_px, _py, 3, 0, Math.PI*2, true);
+ ctx.closePath();
+ ctx.fill();
+ ctx.fillText(label, _px + 5, _py);
+}
+
+function draw(test, title, _at_x, _at_y, scale) {
+ ctx.fillStyle = "rgba(0,0,0, 0.1)";
+ ctx.font = "normal 50px Arial";
+ ctx.fillText(title, 50, 50);
+ ctx.font = "normal 10px Arial";
+
+ var unit = scale * ticks;
+ ctx.lineWidth = 1;
+ var i;
+ for (i = 0; i <= rows * ticks; ++i) {
+ ctx.strokeStyle = (i % ticks) != 0 ? "rgb(160,160,160)" : "black";
+ ctx.beginPath();
+ ctx.moveTo(_at_x + 0, _at_y + i * scale);
+ ctx.lineTo(_at_x + unit * columns, _at_y + i * scale);
+ ctx.stroke();
+ }
+ for (i = 0; i <= columns * ticks; ++i) {
+ ctx.strokeStyle = (i % ticks) != 0 ? "rgb(160,160,160)" : "black";
+ ctx.beginPath();
+ ctx.moveTo(_at_x + i * scale, _at_y + 0);
+ ctx.lineTo(_at_x + i * scale, _at_y + unit * rows);
+ ctx.stroke();
+ }
+
+ var xoffset = xStart * -unit + _at_x;
+ var yoffset = yStart * -unit + _at_y;
+
+ ctx.fillStyle = "rgb(40,80,60)"
+ for (i = 0; i <= columns; i += (1 / ticks))
+ {
+ num = (xoffset - _at_x) / -unit + i;
+ ctx.fillText(num.toFixed(0), i * unit + _at_y - 5, 10);
+ }
+ for (i = 0; i <= rows; i += (1 / ticks))
+ {
+ num = (yoffset - _at_x) / -unit + i;
+ ctx.fillText(num.toFixed(0), 0, i * unit + _at_y + 0);
+ }
+
+ ctx.strokeStyle = "red";
+ var contours, verbs, pts;
+ ctx.beginPath();
+ for (contours in test) {
+ var contour = test[contours];
+ if (contours == 2) ctx.strokeStyle = "blue";
+ var first = true;
+ for (verbs in contour) {
+ var verb = contour[verbs];
+ switch (verb.length) {
+ case 2:
+ if (first) {
+ ctx.moveTo(xoffset + verb[0] * unit, yoffset + verb[1] * unit);
+ first = false;
+ } else
+ ctx.lineTo(xoffset + verb[0] * unit, yoffset + verb[1] * unit);
+ break;
+ case 4:
+ ctx.quadraticCurveTo(xoffset + verb[0] * unit, yoffset + verb[1] * unit,
+ xoffset + verb[2] * unit, yoffset + verb[3] * unit);
+ break;
+ case 6:
+ ctx.bezierCurveTo(xoffset + verb[0] * unit, yoffset + verb[1] * unit,
+ xoffset + verb[2] * unit, yoffset + verb[3] * unit,
+ xoffset + verb[4] * unit, yoffset + verb[5] * unit);
+ break;
+ }
+ }
+ ctx.closePath();
+ }
+ if (hasXor) {
+ ctx.fillType=xor; // how is this done?
+ }
+ ctx.stroke();
+ ctx.fillStyle="rgba(192,192,255, 0.3)";
+ ctx.fill();
+
+ if (!draw_labels) {
+ return;
+ }
+ ctx.fillStyle="blue";
+ for (contours in test) {
+ var contour = test[contours];
+ for (verbs in contour) {
+ var verb = contour[verbs];
+ for (i = 0; i < verb.length; i += 2) {
+ x = verb[i];
+ y = verb[i + 1];
+ drawPoint(x, y, xoffset, yoffset, unit);
+ }
+ }
+ }
+}
+
+var mouseX = Infinity, mouseY;
+
+function calcXY() {
+ var e = window.event;
+ var tgt = e.target || e.srcElement;
+ var left = tgt.offsetLeft;
+ var top = tgt.offsetTop;
+ var unit = scale * ticks;
+ mouseX = (e.clientX - left - Math.ceil(at_x) + 1) / unit + xStart;
+ mouseY = (e.clientY - top - Math.ceil(at_y)) / unit + yStart;
+}
+
+function handleMouseOver() {
+ calcXY();
+ var num = mouseX.toFixed(3) + ", " + mouseY.toFixed(3);
+ ctx.beginPath();
+ ctx.rect(300,100,200,10);
+ ctx.fillStyle="white";
+ ctx.fill();
+ ctx.fillStyle="black";
+ ctx.fillText(num, 300, 108);
+}
+
+function handleMouseClick() {
+ calcXY();
+// drawInset();
+}
+
+function drawTop() {
+ init(tests[testIndex]);
+ redraw();
+}
+
+function redraw() {
+ ctx.beginPath();
+ ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height);
+ ctx.fillStyle="white";
+ ctx.fill();
+ draw(tests[testIndex], testTitles[testIndex], at_x, at_y, scale);
+// if (insetScale != scale && mouseX != Infinity)
+// drawInset();
+}
+
+function doKeyPress(evt) {
+ var char = String.fromCharCode(evt.charCode);
+ switch (char) {
+ case 'N':
+ testIndex += 9;
+ case 'n':
+ if (++testIndex >= tests.length)
+ testIndex = 0;
+ mouseX = Infinity;
+ drawTop();
+ break;
+ case 'P':
+ testIndex -= 9;
+ case 'p':
+ if (--testIndex < 0)
+ testIndex = tests.length - 1;
+ mouseX = Infinity;
+ drawTop();
+ break;
+ case 'T':
+ case 't':
+ break;
+ case '-':
+ drawTop();
+ break;
+ case '=':
+ case '+':
+ drawTop();
+ break;
+ case 'x':
+ draw_labels ^= true;
+ drawTop();
+ break;
+ }
+}
+
+function start() {
+ for (i = 0; i < testDivs.length; ++i) {
+ var title = testDivs[i].id.toString();
+ var str = testDivs[i].firstChild.data;
+ if (str.split("addRect").length > 1) {
+ parseRect(str, title);
+ } else {
+ parse(str, title);
+ }
+ }
+ drawTop();
+ window.addEventListener('keypress', doKeyPress, true);
+ window.onresize = function() {
+ drawTop();
+ }
+}
+
+</script>
+</head>
+
+<body onLoad="start();">
+<canvas id="canvas" width="750" height="500"
+ onmousemove="handleMouseOver()"
+ onclick="handleMouseClick()"
+ ></canvas >
+</body>
+</html>
diff --git a/experimental/pixman/Pixman-version.h b/experimental/pixman/Pixman-version.h
new file mode 100644
index 0000000000..1e3543afa3
--- /dev/null
+++ b/experimental/pixman/Pixman-version.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright © 2008 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Author: Carl D. Worth <cworth@cworth.org>
+ */
+
+#ifndef PIXMAN_VERSION_H__
+#define PIXMAN_VERSION_H__
+
+#ifndef PIXMAN_H__
+# error pixman-version.h should only be included by pixman.h
+#endif
+
+#define PIXMAN_VERSION_MAJOR 0
+#define PIXMAN_VERSION_MINOR 27
+#define PIXMAN_VERSION_MICRO 33
+
+#define PIXMAN_VERSION_STRING "@PIXMAN_VERSION_MAJOR@.@PIXMAN_VERSION_MINOR@.@PIXMAN_VERSION_MICRO@"
+
+#define PIXMAN_VERSION_ENCODE(major, minor, micro) ( \
+ ((major) * 10000) \
+ + ((minor) * 100) \
+ + ((micro) * 1))
+
+#define PIXMAN_VERSION PIXMAN_VERSION_ENCODE( \
+ PIXMAN_VERSION_MAJOR, \
+ PIXMAN_VERSION_MINOR, \
+ PIXMAN_VERSION_MICRO)
+
+#endif /* PIXMAN_VERSION_H__ */
diff --git a/experimental/pixman/junk.cpp b/experimental/pixman/junk.cpp
new file mode 100644
index 0000000000..f85a442e9a
--- /dev/null
+++ b/experimental/pixman/junk.cpp
@@ -0,0 +1,108 @@
+
+extern "C" {
+#include <stdio.h>
+#include <stdlib.h>
+#include <config.h>
+#include "pixman-private.h"
+#include "utils.h"
+#include "gtk-utils.h"
+
+}
+
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkGraphics.h"
+#include "SkPaint.h"
+#import "SkWindow.h"
+
+bool DrawPixman(SkCanvas* canvas, int step, bool useOld);
+SkCanvas* canvas;
+
+extern "C" {
+
+void*
+pixbuf_from_argb32 (uint32_t *bits,
+ int width,
+ int height,
+ int stride)
+{
+ SkBitmap* bitmap = new SkBitmap;
+ bitmap->setConfig(SkBitmap::kARGB_8888_Config, width, height);
+ bitmap->allocPixels();
+
+ int p_stride = bitmap->rowBytes();
+ uint32_t *p_bits = bitmap->getAddr32(0, 0);
+ int i;
+
+ for (i = 0; i < height; ++i)
+ {
+ uint32_t *src_row = &bits[i * (stride / 4)];
+ uint32_t *dst_row = p_bits + i * (p_stride / 4);
+
+ a8r8g8b8_to_rgba_np (dst_row, src_row, width);
+ }
+ return (void*) bitmap;
+}
+
+
+void show_image (pixman_image_t *image) {
+ int width, height;
+ pixman_format_code_t format;
+ pixman_image_t *copy;
+
+ width = pixman_image_get_width (image);
+ height = pixman_image_get_height (image);
+
+
+ format = pixman_image_get_format (image);
+
+ /* Three cases:
+ *
+ * - image is a8r8g8b8_sRGB: we will display without modification
+ * under the assumption that the monitor is sRGB
+ *
+ * - image is a8r8g8b8: we will display without modification
+ * under the assumption that whoever created the image
+ * probably did it wrong by using sRGB inputs
+ *
+ * - other: we will convert to a8r8g8b8 under the assumption that
+ * whoever created the image probably did it wrong.
+ */
+ switch (format)
+ {
+ case PIXMAN_a8r8g8b8_sRGB:
+ case PIXMAN_a8r8g8b8:
+ copy = pixman_image_ref (image);
+ break;
+
+ default:
+ copy = pixman_image_create_bits (PIXMAN_a8r8g8b8,
+ width, height, NULL, -1);
+ pixman_image_composite32 (PIXMAN_OP_SRC,
+ image, NULL, copy,
+ 0, 0, 0, 0, 0, 0,
+ width, height);
+ break;
+ }
+
+ SkBitmap* bitmap = (SkBitmap*) pixbuf_from_argb32 (pixman_image_get_data (copy),
+ width, height,
+ pixman_image_get_stride (copy));
+ canvas->drawBitmap(*bitmap, 0, 0);
+ delete bitmap;
+}
+
+}
+
+bool DrawPixman(SkCanvas* c, int step, bool usePixman) {
+ canvas = c;
+ switch(step) {
+ case 0:
+ checkerboard_main(0, NULL);
+ break;
+ default:
+ alpha_main(0, NULL);
+ break;
+ }
+ return true;
+}
diff --git a/gm/Android.mk b/gm/Android.mk
index 6e1dedd21a..c60e4e701d 100644
--- a/gm/Android.mk
+++ b/gm/Android.mk
@@ -21,15 +21,16 @@ LOCAL_SRC_FILES += \
bitmapscroll.cpp \
blend.cpp \
blurs.cpp \
+ blurrect.cpp \
circles.cpp \
colorfilterimagefilter.cpp \
- cmykjpeg.cpp \
colormatrix.cpp \
complexclip.cpp \
complexclip2.cpp \
composeshader.cpp \
convexpaths.cpp \
cubicpaths.cpp \
+ cmykjpeg.cpp \
degeneratesegments.cpp \
dashcubics.cpp \
dashing.cpp \
@@ -38,6 +39,8 @@ LOCAL_SRC_FILES += \
drawlooper.cpp \
extractbitmap.cpp \
emptypath.cpp \
+ fatpathfill.cpp \
+ factory.cpp \
filltypes.cpp \
filltypespersp.cpp \
fontscaler.cpp \
@@ -57,15 +60,19 @@ LOCAL_SRC_FILES += \
lcdtext.cpp \
linepaths.cpp \
matrixconvolution.cpp \
+ modecolorfilters.cpp \
morphology.cpp \
ninepatchstretch.cpp \
nocolorbleed.cpp \
patheffects.cpp \
pathfill.cpp \
+ pathinterior.cpp \
pathreverse.cpp \
points.cpp \
poly2poly.cpp \
quadpaths.cpp \
+ rrect.cpp \
+ rrects.cpp \
samplerstress.cpp \
shaderbounds.cpp \
shadertext.cpp \
@@ -73,8 +80,9 @@ LOCAL_SRC_FILES += \
shadertext3.cpp \
shadows.cpp \
simpleaaclip.cpp \
+ srcmode.cpp \
strokefill.cpp \
- strokerects.cpp \
+ strokerect.cpp \
strokes.cpp \
tablecolorfilter.cpp \
texteffects.cpp \
@@ -107,7 +115,8 @@ LOCAL_C_INCLUDES := \
external/skia/gm \
external/skia/src/core \
external/skia/src/gpu \
- external/skia/src/pipe/utils
+ external/skia/src/pipe/utils \
+ external/skia/src/utils
LOCAL_MODULE := skia_gm
diff --git a/gm/aaclip.cpp b/gm/aaclip.cpp
index f67ac17a9f..b74f6d3e15 100644
--- a/gm/aaclip.cpp
+++ b/gm/aaclip.cpp
@@ -9,11 +9,27 @@
#include "SkCanvas.h"
#include "SkPath.h"
+// Reproduces bug found here: http://jsfiddle.net/R8Cu5/1/
+//
+#include "SkGradientShader.h"
+static void test_grad(SkCanvas* canvas) {
+ SkPoint pts[] = {
+ { 478.544067f, -84.2041016f },
+ { 602.455933f, 625.204102f },
+ };
+ SkColor colors[] = { SK_ColorBLACK, SK_ColorBLACK, SK_ColorRED, SK_ColorRED };
+ SkScalar pos[] = { 0, 0.3f, 0.3f, 1.0f };
+ SkShader* s = SkGradientShader::CreateLinear(pts, colors, pos, 4, SkShader::kClamp_TileMode);
+ SkPaint p;
+ p.setShader(s)->unref();
+ canvas->drawPaint(p);
+}
+
static SkCanvas* MakeCanvas(const SkIRect& bounds) {
SkBitmap bm;
bm.setConfig(SkBitmap::kARGB_8888_Config, bounds.width(), bounds.height());
bm.allocPixels();
- bm.eraseColor(0);
+ bm.eraseColor(SK_ColorTRANSPARENT);
SkCanvas* canvas = new SkCanvas(bm);
canvas->translate(-SkIntToScalar(bounds.fLeft), -SkIntToScalar(bounds.fTop));
@@ -158,6 +174,9 @@ protected:
}
virtual void onDraw(SkCanvas* canvas) {
+ if (false) {
+ test_grad(canvas); return;
+ }
if (false) { // avoid bit rot, suppress warning
test_mask();
}
diff --git a/gm/aarectmodes.cpp b/gm/aarectmodes.cpp
index 07cfa121f2..56343240d6 100644
--- a/gm/aarectmodes.cpp
+++ b/gm/aarectmodes.cpp
@@ -177,9 +177,6 @@ namespace skiagm {
}
}
- // disable pdf for now, since it crashes on mac
- virtual uint32_t onGetFlags() const { return kSkipPDF_Flag; }
-
private:
typedef GM INHERITED;
};
diff --git a/gm/arithmode.cpp b/gm/arithmode.cpp
index 6f10dd6beb..f5e1091eff 100644
--- a/gm/arithmode.cpp
+++ b/gm/arithmode.cpp
@@ -19,7 +19,7 @@ static SkBitmap make_bm() {
SkBitmap bm;
bm.setConfig(SkBitmap::kARGB_8888_Config, WW, HH);
bm.allocPixels();
- bm.eraseColor(0);
+ bm.eraseColor(SK_ColorTRANSPARENT);
return bm;
}
diff --git a/gm/bitmaprect.cpp b/gm/bitmaprect.cpp
index 4af6f0bd7d..9c6d472b57 100644
--- a/gm/bitmaprect.cpp
+++ b/gm/bitmaprect.cpp
@@ -94,9 +94,9 @@ static void make_3x3_bitmap(SkBitmap* bitmap) {
static const int gYSize = 3;
SkColor textureData[gXSize][gYSize] = {
- SK_ColorRED, SK_ColorWHITE, SK_ColorBLUE,
- SK_ColorGREEN, SK_ColorBLACK, SK_ColorCYAN,
- SK_ColorYELLOW, SK_ColorGRAY, SK_ColorMAGENTA
+ { SK_ColorRED, SK_ColorWHITE, SK_ColorBLUE },
+ { SK_ColorGREEN, SK_ColorBLACK, SK_ColorCYAN },
+ { SK_ColorYELLOW, SK_ColorGRAY, SK_ColorMAGENTA }
};
diff --git a/gm/blurrect.cpp b/gm/blurrect.cpp
new file mode 100644
index 0000000000..c28468acf9
--- /dev/null
+++ b/gm/blurrect.cpp
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+#include "SkBlurMaskFilter.h"
+#include "SkCanvas.h"
+#include "SkPath.h"
+
+#define STROKE_WIDTH SkIntToScalar(10)
+
+typedef void (*Proc)(SkCanvas*, const SkRect&, const SkPaint&);
+
+static void fill_rect(SkCanvas* canvas, const SkRect& r, const SkPaint& p) {
+ canvas->drawRect(r, p);
+}
+
+static void stroke_rect(SkCanvas* canvas, const SkRect& r, const SkPaint& p) {
+ SkPaint paint(p);
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setStrokeWidth(STROKE_WIDTH);
+ canvas->drawRect(r, paint);
+}
+
+static void draw_donut(SkCanvas* canvas, const SkRect& r, const SkPaint& p) {
+ SkRect rect;
+ SkPath path;
+
+ rect = r;
+ rect.outset(STROKE_WIDTH/2, STROKE_WIDTH/2);
+ path.addRect(rect);
+ rect = r;
+ rect.inset(STROKE_WIDTH/2, STROKE_WIDTH/2);
+
+ path.addRect(rect);
+ path.setFillType(SkPath::kEvenOdd_FillType);
+
+ canvas->drawPath(path, p);
+}
+
+static void draw_donut_skewed(SkCanvas* canvas, const SkRect& r, const SkPaint& p) {
+ SkRect rect;
+ SkPath path;
+
+ rect = r;
+ rect.outset(STROKE_WIDTH/2, STROKE_WIDTH/2);
+ path.addRect(rect);
+ rect = r;
+ rect.inset(STROKE_WIDTH/2, STROKE_WIDTH/2);
+
+ rect.offset(7, -7);
+
+ path.addRect(rect);
+ path.setFillType(SkPath::kEvenOdd_FillType);
+
+ canvas->drawPath(path, p);
+}
+
+#include "SkGradientShader.h"
+
+typedef void (*PaintProc)(SkPaint*, SkScalar width);
+
+static void setgrad(SkPaint* paint, SkScalar width) {
+ SkPoint pts[] = { { 0, 0 }, { width, 0 } };
+ SkColor colors[] = { SK_ColorRED, SK_ColorGREEN };
+ SkShader* s = SkGradientShader::CreateLinear(pts, colors, NULL, 2,
+ SkShader::kClamp_TileMode);
+ paint->setShader(s)->unref();
+}
+
+static const char* gBlurStyle2Name[] = {
+ "normal",
+ "solid",
+ "outer",
+ "inner"
+};
+
+class BlurRectGM : public skiagm::GM {
+ SkAutoTUnref<SkMaskFilter> fMaskFilter;
+ SkString fName;
+ PaintProc fPProc;
+ SkAlpha fAlpha;
+public:
+ BlurRectGM(const char name[], PaintProc pproc, U8CPU alpha,
+ SkBlurMaskFilter::BlurStyle bs) :
+ fMaskFilter(SkBlurMaskFilter::Create(STROKE_WIDTH/2, bs,
+ SkBlurMaskFilter::kHighQuality_BlurFlag))
+ , fName(name)
+ , fPProc(pproc)
+ , fAlpha(SkToU8(alpha))
+ {
+ fName.appendf("_%s", gBlurStyle2Name[bs]);
+ }
+
+protected:
+ virtual SkString onShortName() {
+ return fName;
+ }
+
+ virtual SkISize onISize() {
+ return SkISize::Make(640, 480);
+ }
+
+ virtual void onDraw(SkCanvas* canvas) {
+ canvas->translate(STROKE_WIDTH*3/2, STROKE_WIDTH*3/2);
+
+ SkRect r = { 0, 0, 250, 120 };
+
+ SkPaint paint;
+ paint.setMaskFilter(fMaskFilter);
+ if (fPProc) {
+ fPProc(&paint, r.width());
+ }
+ paint.setAlpha(fAlpha);
+
+ static const Proc procs[] = {
+ fill_rect, draw_donut, draw_donut_skewed
+ };
+
+ this->drawProcs(canvas, r, paint, false, procs, SK_ARRAY_COUNT(procs));
+ canvas->translate(r.width() * 4/3, 0);
+ this->drawProcs(canvas, r, paint, true, procs, SK_ARRAY_COUNT(procs));
+ }
+
+ virtual uint32_t onGetFlags() const { return kSkipPipe_Flag; }
+
+private:
+ void drawProcs(SkCanvas* canvas, const SkRect& r, const SkPaint& paint,
+ bool doClip, const Proc procs[], size_t procsCount) {
+ SkAutoCanvasRestore acr(canvas, true);
+ for (size_t i = 0; i < procsCount; ++i) {
+ if (doClip) {
+ SkRect clipRect(r);
+ clipRect.inset(STROKE_WIDTH/2, STROKE_WIDTH/2);
+ canvas->save();
+ canvas->clipRect(r);
+ }
+ procs[i](canvas, r, paint);
+ if (doClip) {
+ canvas->restore();
+ }
+ canvas->translate(0, r.height() * 4/3);
+ }
+ }
+
+ typedef GM INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+DEF_GM(return new BlurRectGM("blurrect", NULL, 0xFF, SkBlurMaskFilter::kNormal_BlurStyle);)
+DEF_GM(return new BlurRectGM("blurrect", NULL, 0xFF, SkBlurMaskFilter::kSolid_BlurStyle);)
+DEF_GM(return new BlurRectGM("blurrect", NULL, 0xFF, SkBlurMaskFilter::kOuter_BlurStyle);)
+DEF_GM(return new BlurRectGM("blurrect", NULL, 0xFF, SkBlurMaskFilter::kInner_BlurStyle);)
+
+DEF_GM(return new BlurRectGM("blurrect_grad_80", setgrad, 0x80, SkBlurMaskFilter::kNormal_BlurStyle);)
+
diff --git a/gm/cmykjpeg.cpp b/gm/cmykjpeg.cpp
index 94cf98f92d..692bc3e760 100644
--- a/gm/cmykjpeg.cpp
+++ b/gm/cmykjpeg.cpp
@@ -17,7 +17,10 @@ namespace skiagm {
*/
class CMYKJpegGM : public GM {
public:
- CMYKJpegGM() {
+ CMYKJpegGM() {}
+
+protected:
+ virtual void onOnceBeforeDraw() SK_OVERRIDE {
// parameters to the "decode" call
bool dither = false;
@@ -41,7 +44,6 @@ public:
}
}
-protected:
virtual SkString onShortName() {
return SkString("cmykjpeg");
}
diff --git a/gm/colorfilterimagefilter.cpp b/gm/colorfilterimagefilter.cpp
index 556e88a186..60148b36f9 100644
--- a/gm/colorfilterimagefilter.cpp
+++ b/gm/colorfilterimagefilter.cpp
@@ -29,7 +29,7 @@ static SkImageFilter* make_brightness(float amount, SkImageFilter* input = NULL)
0, 0, 1, 0, amount255,
0, 0, 0, 1, 0 };
SkAutoTUnref<SkColorFilter> filter(new SkColorMatrixFilter(matrix));
- return new SkColorFilterImageFilter(filter, input);
+ return SkColorFilterImageFilter::Create(filter, input);
}
static SkImageFilter* make_grayscale(SkImageFilter* input = NULL) {
@@ -40,13 +40,13 @@ static SkImageFilter* make_grayscale(SkImageFilter* input = NULL) {
matrix[2] = matrix[7] = matrix[12] = SkFloatToScalar(0.0722f);
matrix[18] = SkFloatToScalar(1.0f);
SkAutoTUnref<SkColorFilter> filter(new SkColorMatrixFilter(matrix));
- return new SkColorFilterImageFilter(filter, input);
+ return SkColorFilterImageFilter::Create(filter, input);
}
static SkImageFilter* make_mode_blue(SkImageFilter* input = NULL) {
SkAutoTUnref<SkColorFilter> filter(
SkColorFilter::CreateModeFilter(SK_ColorBLUE, SkXfermode::kSrcIn_Mode));
- return new SkColorFilterImageFilter(filter, input);
+ return SkColorFilterImageFilter::Create(filter, input);
}
class ColorFilterImageFilterGM : public skiagm::GM {
diff --git a/gm/colormatrix.cpp b/gm/colormatrix.cpp
index af305ee04f..16086f0346 100644
--- a/gm/colormatrix.cpp
+++ b/gm/colormatrix.cpp
@@ -7,6 +7,7 @@
#include "gm.h"
#include "SkColorMatrixFilter.h"
+#include "SkGradientShader.h"
#define WIDTH 500
#define HEIGHT 500
@@ -41,7 +42,8 @@ class ColorMatrixGM : public GM {
SkOnce fOnce;
void init() {
if (fOnce.once()) {
- fBitmap = createBitmap(64, 64);
+ fSolidBitmap = this->createSolidBitmap(64, 64);
+ fTransparentBitmap = this->createTransparentBitmap(64, 64);
}
}
@@ -59,7 +61,7 @@ protected:
return make_isize(WIDTH, HEIGHT);
}
- SkBitmap createBitmap(int width, int height) {
+ SkBitmap createSolidBitmap(int width, int height) {
SkBitmap bm;
bm.setConfig(SkBitmap::kARGB_8888_Config, width, height);
bm.allocPixels();
@@ -75,68 +77,95 @@ protected:
}
return bm;
}
+
+ // creates a bitmap with shades of transparent gray.
+ SkBitmap createTransparentBitmap(int width, int height) {
+ SkBitmap bm;
+ bm.setConfig(SkBitmap::kARGB_8888_Config, width, height);
+ bm.allocPixels();
+ SkCanvas canvas(bm);
+ canvas.clear(0x0);
+
+ SkPoint pts[] = {{0, 0}, {SkIntToScalar(width), SkIntToScalar(height)}};
+ SkColor colors[] = {0x00000000, 0xFFFFFFFF};
+ SkPaint paint;
+ paint.setShader(SkGradientShader::CreateLinear(pts, colors, NULL, 2,
+ SkShader::kClamp_TileMode))->unref();
+ canvas.drawRect(SkRect::MakeWH(SkIntToScalar(width), SkIntToScalar(height)), paint);
+ return bm;
+ }
+
virtual void onDraw(SkCanvas* canvas) {
this->init();
SkPaint paint;
SkColorMatrix matrix;
- matrix.setIdentity();
- setColorMatrix(&paint, matrix);
- canvas->drawBitmap(fBitmap, 0, 0, &paint);
-
- matrix.setRotate(SkColorMatrix::kR_Axis, 90);
- setColorMatrix(&paint, matrix);
- canvas->drawBitmap(fBitmap, 80, 0, &paint);
-
- matrix.setRotate(SkColorMatrix::kG_Axis, 90);
- setColorMatrix(&paint, matrix);
- canvas->drawBitmap(fBitmap, 160, 0, &paint);
-
- matrix.setRotate(SkColorMatrix::kB_Axis, 90);
- setColorMatrix(&paint, matrix);
- canvas->drawBitmap(fBitmap, 240, 0, &paint);
-
- matrix.setSaturation(SkFloatToScalar(0.0f));
- setColorMatrix(&paint, matrix);
- canvas->drawBitmap(fBitmap, 0, 80, &paint);
-
- matrix.setSaturation(SkFloatToScalar(0.5f));
- setColorMatrix(&paint, matrix);
- canvas->drawBitmap(fBitmap, 80, 80, &paint);
-
- matrix.setSaturation(SkFloatToScalar(1.0f));
- setColorMatrix(&paint, matrix);
- canvas->drawBitmap(fBitmap, 160, 80, &paint);
-
- matrix.setSaturation(SkFloatToScalar(2.0f));
- setColorMatrix(&paint, matrix);
- canvas->drawBitmap(fBitmap, 240, 80, &paint);
-
- matrix.setRGB2YUV();
- setColorMatrix(&paint, matrix);
- canvas->drawBitmap(fBitmap, 0, 160, &paint);
-
- matrix.setYUV2RGB();
- setColorMatrix(&paint, matrix);
- canvas->drawBitmap(fBitmap, 80, 160, &paint);
-
- SkScalar s1 = SK_Scalar1;
- SkScalar s255 = SkIntToScalar(255);
- // Move red into alpha, set color to white
- SkScalar data[20] = {
- 0, 0, 0, 0, s255,
- 0, 0, 0, 0, s255,
- 0, 0, 0, 0, s255,
- s1, 0, 0, 0, 0,
- };
-
- setArray(&paint, data);
- canvas->drawBitmap(fBitmap, 160, 160, &paint);
+ paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+ const SkBitmap bmps[] = { fSolidBitmap, fTransparentBitmap };
+
+ for (size_t i = 0; i < SK_ARRAY_COUNT(bmps); ++i) {
+
+ matrix.setIdentity();
+ setColorMatrix(&paint, matrix);
+ canvas->drawBitmap(bmps[i], 0, 0, &paint);
+
+ matrix.setRotate(SkColorMatrix::kR_Axis, 90);
+ setColorMatrix(&paint, matrix);
+ canvas->drawBitmap(bmps[i], 80, 0, &paint);
+
+ matrix.setRotate(SkColorMatrix::kG_Axis, 90);
+ setColorMatrix(&paint, matrix);
+ canvas->drawBitmap(bmps[i], 160, 0, &paint);
+
+ matrix.setRotate(SkColorMatrix::kB_Axis, 90);
+ setColorMatrix(&paint, matrix);
+ canvas->drawBitmap(bmps[i], 240, 0, &paint);
+
+ matrix.setSaturation(SkFloatToScalar(0.0f));
+ setColorMatrix(&paint, matrix);
+ canvas->drawBitmap(bmps[i], 0, 80, &paint);
+
+ matrix.setSaturation(SkFloatToScalar(0.5f));
+ setColorMatrix(&paint, matrix);
+ canvas->drawBitmap(bmps[i], 80, 80, &paint);
+
+ matrix.setSaturation(SkFloatToScalar(1.0f));
+ setColorMatrix(&paint, matrix);
+ canvas->drawBitmap(bmps[i], 160, 80, &paint);
+
+ matrix.setSaturation(SkFloatToScalar(2.0f));
+ setColorMatrix(&paint, matrix);
+ canvas->drawBitmap(bmps[i], 240, 80, &paint);
+
+ matrix.setRGB2YUV();
+ setColorMatrix(&paint, matrix);
+ canvas->drawBitmap(bmps[i], 0, 160, &paint);
+
+ matrix.setYUV2RGB();
+ setColorMatrix(&paint, matrix);
+ canvas->drawBitmap(bmps[i], 80, 160, &paint);
+
+ SkScalar s1 = SK_Scalar1;
+ SkScalar s255 = SkIntToScalar(255);
+ // Move red into alpha, set color to white
+ SkScalar data[20] = {
+ 0, 0, 0, 0, s255,
+ 0, 0, 0, 0, s255,
+ 0, 0, 0, 0, s255,
+ s1, 0, 0, 0, 0,
+ };
+
+ setArray(&paint, data);
+ canvas->drawBitmap(bmps[i], 160, 160, &paint);
+
+ canvas->translate(0, 240);
+ }
}
private:
- SkBitmap fBitmap;
+ SkBitmap fSolidBitmap;
+ SkBitmap fTransparentBitmap;
typedef GM INHERITED;
};
diff --git a/gm/dashing.cpp b/gm/dashing.cpp
index 2f1f0260c9..70fb9b1fa2 100644
--- a/gm/dashing.cpp
+++ b/gm/dashing.cpp
@@ -171,9 +171,135 @@ protected:
//////////////////////////////////////////////////////////////////////////////
+// Test out the on/off line dashing Chrome if fond of
+class Dashing3GM : public skiagm::GM {
+public:
+ Dashing3GM() {}
+
+protected:
+ SkString onShortName() {
+ return SkString("dashing3");
+ }
+
+ SkISize onISize() { return skiagm::make_isize(640, 480); }
+
+ // Draw a 100x100 block of dashed lines. The horizontal ones are BW
+ // while the vertical ones are AA.
+ void drawDashedLines(SkCanvas* canvas,
+ SkScalar lineLength,
+ SkScalar phase,
+ SkScalar dashLength,
+ int strokeWidth,
+ bool circles) {
+ SkPaint p;
+ p.setColor(SK_ColorBLACK);
+ p.setStyle(SkPaint::kStroke_Style);
+ p.setStrokeWidth(SkIntToScalar(strokeWidth));
+
+ if (circles) {
+ p.setStrokeCap(SkPaint::kRound_Cap);
+ }
+
+ SkScalar intervals[2] = { dashLength, dashLength };
+
+ p.setPathEffect(new SkDashPathEffect(intervals, 2, phase, false));
+
+ SkPoint pts[2];
+
+ for (int y = 0; y < 100; y += 10*strokeWidth) {
+ pts[0].set(0, SkIntToScalar(y));
+ pts[1].set(lineLength, SkIntToScalar(y));
+
+ canvas->drawPoints(SkCanvas::kLines_PointMode, 2, pts, p);
+ }
+
+ p.setAntiAlias(true);
+
+ for (int x = 0; x < 100; x += 14*strokeWidth) {
+ pts[0].set(SkIntToScalar(x), 0);
+ pts[1].set(SkIntToScalar(x), lineLength);
+
+ canvas->drawPoints(SkCanvas::kLines_PointMode, 2, pts, p);
+ }
+ }
+
+ virtual void onDraw(SkCanvas* canvas) {
+ // 1on/1off 1x1 squares with phase of 0 - points fastpath
+ canvas->save();
+ canvas->translate(2, 0);
+ this->drawDashedLines(canvas, 100, 0, SK_Scalar1, 1, false);
+ canvas->restore();
+
+ // 1on/1off 1x1 squares with phase of .5 - rects fastpath (due to partial squares)
+ canvas->save();
+ canvas->translate(112, 0);
+ this->drawDashedLines(canvas, 100, SK_ScalarHalf, SK_Scalar1, 1, false);
+ canvas->restore();
+
+ // 1on/1off 1x1 squares with phase of 1 - points fastpath
+ canvas->save();
+ canvas->translate(222, 0);
+ this->drawDashedLines(canvas, 100, SK_Scalar1, SK_Scalar1, 1, false);
+ canvas->restore();
+
+ // 1on/1off 1x1 squares with phase of 1 and non-integer length - rects fastpath
+ canvas->save();
+ canvas->translate(332, 0);
+ this->drawDashedLines(canvas, 99.5, SK_ScalarHalf, SK_Scalar1, 1, false);
+ canvas->restore();
+
+ // 1on/1off 3x3 squares with phase of 0 - points fast path
+ canvas->save();
+ canvas->translate(2, 110);
+ this->drawDashedLines(canvas, 100, 0, SkIntToScalar(3), 3, false);
+ canvas->restore();
+
+ // 1on/1off 3x3 squares with phase of 1.5 - rects fast path
+ canvas->save();
+ canvas->translate(112, 110);
+ this->drawDashedLines(canvas, 100, SkFloatToScalar(1.5f), SkIntToScalar(3), 3, false);
+ canvas->restore();
+
+ // 1on/1off 1x1 circles with phase of 1 - no fast path yet
+ canvas->save();
+ canvas->translate(2, 220);
+ this->drawDashedLines(canvas, 100, SK_Scalar1, SK_Scalar1, 1, true);
+ canvas->restore();
+
+ // 1on/1off 3x3 circles with phase of 1 - no fast path yet
+ canvas->save();
+ canvas->translate(112, 220);
+ this->drawDashedLines(canvas, 100, 0, SkIntToScalar(3), 3, true);
+ canvas->restore();
+
+ // 1on/1off 1x1 squares with rotation - should break fast path
+ canvas->save();
+ canvas->translate(332+SK_ScalarRoot2Over2*100, 110+SK_ScalarRoot2Over2*100);
+ canvas->rotate(45);
+ canvas->translate(-50, -50);
+
+ this->drawDashedLines(canvas, 100, SK_Scalar1, SK_Scalar1, 1, false);
+ canvas->restore();
+
+ // 3on/3off 3x1 rects - should use rect fast path regardless of phase
+ for (int phase = 0; phase <= 3; ++phase) {
+ canvas->save();
+ canvas->translate(SkIntToScalar(phase*110+2),
+ SkIntToScalar(330));
+ this->drawDashedLines(canvas, 100, SkIntToScalar(phase), SkIntToScalar(3), 1, false);
+ canvas->restore();
+ }
+ }
+
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
static skiagm::GM* F0(void*) { return new DashingGM; }
static skiagm::GM* F1(void*) { return new Dashing2GM; }
+static skiagm::GM* F2(void*) { return new Dashing3GM; }
static skiagm::GMRegistry gR0(F0);
static skiagm::GMRegistry gR1(F1);
+static skiagm::GMRegistry gR2(F2);
diff --git a/gm/drawbitmaprect.cpp b/gm/drawbitmaprect.cpp
index f9b348faf9..24c34318a1 100644
--- a/gm/drawbitmaprect.cpp
+++ b/gm/drawbitmaprect.cpp
@@ -34,7 +34,7 @@ static SkBitmap make_chessbm(int w, int h) {
static void makebm(SkBitmap* bm, SkBitmap::Config config, int w, int h) {
bm->setConfig(config, w, h);
bm->allocPixels();
- bm->eraseColor(0);
+ bm->eraseColor(SK_ColorTRANSPARENT);
SkCanvas canvas(*bm);
diff --git a/gm/factory.cpp b/gm/factory.cpp
new file mode 100644
index 0000000000..88931584d8
--- /dev/null
+++ b/gm/factory.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+#include "SkBitmapFactory.h"
+#include "SkCanvas.h"
+#include "SkData.h"
+#include "SkStream.h"
+
+namespace skiagm {
+
+/**
+ * Draw a PNG created by SkBitmapFactory.
+ */
+class FactoryGM : public GM {
+public:
+ FactoryGM() {}
+
+protected:
+ virtual void onOnceBeforeDraw() SK_OVERRIDE {
+ SkString filename(INHERITED::gResourcePath);
+ if (!filename.endsWith("/") && !filename.endsWith("\\")) {
+ filename.append("/");
+ }
+
+ // Copyright-free file from http://openclipart.org/detail/29213/paper-plane-by-ddoo
+ filename.append("plane.png");
+
+ SkFILEStream stream(filename.c_str());
+ if (stream.isValid()) {
+ stream.rewind();
+ size_t length = stream.getLength();
+ void* buffer = sk_malloc_throw(length);
+ stream.read(buffer, length);
+ SkAutoDataUnref data(SkData::NewFromMalloc(buffer, length));
+ SkBitmapFactory::DecodeBitmap(&fBitmap, data);
+ }
+ }
+
+ virtual SkString onShortName() {
+ return SkString("factory");
+ }
+
+ virtual SkISize onISize() {
+ return make_isize(640, 480);
+ }
+
+ virtual void onDraw(SkCanvas* canvas) {
+ canvas->drawBitmap(fBitmap, 0, 0);
+ }
+
+private:
+ SkBitmap fBitmap;
+
+ typedef GM INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static GM* MyFactory(void*) { return new FactoryGM; }
+static GMRegistry reg(MyFactory);
+
+}
diff --git a/gm/fatpathfill.cpp b/gm/fatpathfill.cpp
new file mode 100644
index 0000000000..4c30949f52
--- /dev/null
+++ b/gm/fatpathfill.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+#include "SkCanvas.h"
+#include "SkPath.h"
+#include "SkSurface.h"
+
+#define ZOOM 32
+#define SMALL_W 9
+#define SMALL_H 3
+#define REPEAT_LOOP 5
+
+static SkSurface* new_surface(int width, int height) {
+ SkImage::Info info = {
+ width,
+ height,
+ SkImage::kPMColor_ColorType,
+ SkImage::kPremul_AlphaType
+ };
+ return SkSurface::NewRaster(info);
+}
+
+static void draw_pixel_centers(SkCanvas* canvas) {
+ SkPaint paint;
+ paint.setColor(0xFF0088FF);
+ paint.setAntiAlias(true);
+
+ for (int y = 0; y < SMALL_H; ++y) {
+ for (int x = 0; x < SMALL_W; ++x) {
+ canvas->drawCircle(x + 0.5f, y + 0.5f, 1.5f / ZOOM, paint);
+ }
+ }
+}
+
+static void draw_fatpath(SkCanvas* canvas, SkSurface* surface,
+ const SkPath paths[], int count) {
+ SkPaint paint;
+
+ surface->getCanvas()->clear(SK_ColorTRANSPARENT);
+ for (int i = 0; i < count; ++i) {
+ surface->getCanvas()->drawPath(paths[i], paint);
+ }
+ surface->draw(canvas, 0, 0, NULL);
+
+ paint.setAntiAlias(true);
+ paint.setColor(SK_ColorRED);
+ paint.setStyle(SkPaint::kStroke_Style);
+ for (int j = 0; j < count; ++j) {
+ canvas->drawPath(paths[j], paint);
+ }
+
+ draw_pixel_centers(canvas);
+}
+
+class FatPathFillGM : public skiagm::GM {
+public:
+ FatPathFillGM() {}
+
+protected:
+ virtual SkString onShortName() {
+ return SkString("fatpathfill");
+ }
+
+ virtual SkISize onISize() {
+ return SkISize::Make(SMALL_W * ZOOM, SMALL_H * ZOOM * REPEAT_LOOP);
+ }
+
+ virtual void onDraw(SkCanvas* canvas) {
+ SkAutoTUnref<SkSurface> surface(new_surface(SMALL_W, SMALL_H));
+
+ canvas->scale(ZOOM, ZOOM);
+
+ SkPaint paint;
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setStrokeWidth(SK_Scalar1);
+
+ for (int i = 0; i < REPEAT_LOOP; ++i) {
+ SkPath line, path;
+ line.moveTo(SkIntToScalar(1), SkIntToScalar(2));
+ line.lineTo(SkIntToScalar(4 + i), SkIntToScalar(1));
+ paint.getFillPath(line, &path);
+ draw_fatpath(canvas, surface, &path, 1);
+
+ canvas->translate(0, SMALL_H);
+ }
+ }
+
+private:
+ typedef skiagm::GM INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+DEF_GM(return new FatPathFillGM;)
+
diff --git a/gm/gm.cpp b/gm/gm.cpp
index b6f21f81ff..c1e75e9d43 100644
--- a/gm/gm.cpp
+++ b/gm/gm.cpp
@@ -12,6 +12,8 @@ SkString GM::gResourcePath;
GM::GM() {
fBGColor = SK_ColorWHITE;
+ fCanvasIsDeferred = false;
+ fHaveCalledOnceBeforeDraw = false;
}
GM::~GM() {}
@@ -21,10 +23,18 @@ void GM::draw(SkCanvas* canvas) {
}
void GM::drawContent(SkCanvas* canvas) {
+ if (!fHaveCalledOnceBeforeDraw) {
+ fHaveCalledOnceBeforeDraw = true;
+ this->onOnceBeforeDraw();
+ }
this->onDraw(canvas);
}
void GM::drawBackground(SkCanvas* canvas) {
+ if (!fHaveCalledOnceBeforeDraw) {
+ fHaveCalledOnceBeforeDraw = true;
+ this->onOnceBeforeDraw();
+ }
this->onDrawBackground(canvas);
}
@@ -40,7 +50,7 @@ void GM::setBGColor(SkColor color) {
}
void GM::onDrawBackground(SkCanvas* canvas) {
- canvas->drawColor(fBGColor);
+ canvas->drawColor(fBGColor, SkXfermode::kSrc_Mode);
}
void GM::drawSizeBounds(SkCanvas* canvas, SkColor color) {
diff --git a/gm/gm.h b/gm/gm.h
index 453f104bdb..9460ec01ab 100644
--- a/gm/gm.h
+++ b/gm/gm.h
@@ -16,6 +16,10 @@
#include "SkString.h"
#include "SkTRegistry.h"
+#define DEF_GM(code) \
+ static skiagm::GM* SK_MACRO_APPEND_LINE(F_)(void* p) { code; } \
+ static skiagm::GMRegistry SK_MACRO_APPEND_LINE(R_)(SK_MACRO_APPEND_LINE(F_));
+
namespace skiagm {
static inline SkISize make_isize(int w, int h) {
@@ -67,9 +71,15 @@ namespace skiagm {
gResourcePath = resourcePath;
}
+ bool isCanvasDeferred() const { return fCanvasIsDeferred; }
+ void setCanvasIsDeferred(bool isDeferred) {
+ fCanvasIsDeferred = isDeferred;
+ }
+
protected:
static SkString gResourcePath;
+ virtual void onOnceBeforeDraw() {}
virtual void onDraw(SkCanvas*) = 0;
virtual void onDrawBackground(SkCanvas*);
virtual SkISize onISize() = 0;
@@ -80,6 +90,8 @@ namespace skiagm {
private:
SkString fShortName;
SkColor fBGColor;
+ bool fCanvasIsDeferred; // work-around problem in srcmode.cpp
+ bool fHaveCalledOnceBeforeDraw;
};
typedef SkTRegistry<GM*, void*> GMRegistry;
diff --git a/gm/gmmain.cpp b/gm/gmmain.cpp
index 3cac1e4649..f66fffe105 100644
--- a/gm/gmmain.cpp
+++ b/gm/gmmain.cpp
@@ -5,22 +5,36 @@
* found in the LICENSE file.
*/
+/*
+ * Code for the "gm" (Golden Master) rendering comparison tool.
+ *
+ * If you make changes to this, re-run the self-tests at gm/tests/run.sh
+ * to make sure they still pass... you may need to change the expected
+ * results of the self-test.
+ */
+
#include "gm.h"
#include "system_preferences.h"
+#include "SkBitmapChecksummer.h"
#include "SkColorPriv.h"
#include "SkData.h"
#include "SkDeferredCanvas.h"
#include "SkDevice.h"
+#include "SkDrawFilter.h"
#include "SkGPipe.h"
#include "SkGraphics.h"
#include "SkImageDecoder.h"
#include "SkImageEncoder.h"
+#include "SkOSFile.h"
#include "SkPicture.h"
#include "SkRefCnt.h"
#include "SkStream.h"
#include "SkTArray.h"
+#include "SkTileGridPicture.h"
#include "SamplePipeControllers.h"
+#include "json/value.h"
+
#if SK_SUPPORT_GPU
#include "GrContextFactory.h"
#include "GrRenderTarget.h"
@@ -63,11 +77,38 @@ const static ErrorBitfield ERROR_DIMENSION_MISMATCH = 0x04;
const static ErrorBitfield ERROR_READING_REFERENCE_IMAGE = 0x08;
const static ErrorBitfield ERROR_WRITING_REFERENCE_IMAGE = 0x10;
-// If true, emit a messange when we can't find a reference image to compare
-static bool gNotifyMissingReadReference;
+const static char kJsonKey_ActualResults[] = "actual-results";
+const static char kJsonKey_ActualResults_Failed[] = "failed";
+const static char kJsonKey_ActualResults_FailureIgnored[]= "failure-ignored";
+const static char kJsonKey_ActualResults_Succeeded[] = "succeeded";
+const static char kJsonKey_ActualResults_AnyStatus_Checksum[] = "checksum";
+
+const static char kJsonKey_ExpectedResults[] = "expected-results";
+const static char kJsonKey_ExpectedResults_Checksums[] = "checksums";
+const static char kJsonKey_ExpectedResults_IgnoreFailure[] = "ignore-failure";
using namespace skiagm;
+/*
+ * Return the max of the difference (in absolute value) for any component.
+ * Returns 0 if they are equal.
+ */
+static int compute_PMColor_maxDiff(SkPMColor c0, SkPMColor c1) {
+ int da = SkAbs32(SkGetPackedA32(c0) - SkGetPackedA32(c1));
+ int dr = SkAbs32(SkGetPackedR32(c0) - SkGetPackedR32(c1));
+ int dg = SkAbs32(SkGetPackedG32(c0) - SkGetPackedG32(c1));
+ int db = SkAbs32(SkGetPackedB32(c0) - SkGetPackedB32(c1));
+ return SkMax32(da, SkMax32(dr, SkMax32(dg, db)));
+}
+
+struct FailRec {
+ SkString fName;
+ int fMaxPixelError;
+
+ FailRec() : fMaxPixelError(0) {}
+ FailRec(const SkString& name) : fName(name), fMaxPixelError(0) {}
+};
+
class Iter {
public:
Iter() {
@@ -101,148 +142,24 @@ private:
const GMRegistry* fReg;
};
-static SkString make_name(const char shortName[], const char configName[]) {
- SkString name(shortName);
- name.appendf("_%s", configName);
- return name;
-}
-
-static SkString make_filename(const char path[],
- const char pathSuffix[],
- const SkString& name,
- const char suffix[]) {
- SkString filename(path);
- if (filename.endsWith("/")) {
- filename.remove(filename.size() - 1, 1);
- }
- filename.append(pathSuffix);
- filename.append("/");
- filename.appendf("%s.%s", name.c_str(), suffix);
- return filename;
-}
-
-/* since PNG insists on unpremultiplying our alpha, we take no precision chances
- and force all pixels to be 100% opaque, otherwise on compare we may not get
- a perfect match.
- */
-static void force_all_opaque(const SkBitmap& bitmap) {
- SkAutoLockPixels lock(bitmap);
- for (int y = 0; y < bitmap.height(); y++) {
- for (int x = 0; x < bitmap.width(); x++) {
- *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
- }
- }
-}
-
-static bool write_bitmap(const SkString& path, const SkBitmap& bitmap) {
- SkBitmap copy;
- bitmap.copyTo(&copy, SkBitmap::kARGB_8888_Config);
- force_all_opaque(copy);
- return SkImageEncoder::EncodeFile(path.c_str(), copy,
- SkImageEncoder::kPNG_Type, 100);
-}
-
-static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
- int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
- int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
- int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
- return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
-}
-
-static void compute_diff(const SkBitmap& target, const SkBitmap& base,
- SkBitmap* diff) {
- SkAutoLockPixels alp(*diff);
-
- const int w = target.width();
- const int h = target.height();
- for (int y = 0; y < h; y++) {
- for (int x = 0; x < w; x++) {
- SkPMColor c0 = *base.getAddr32(x, y);
- SkPMColor c1 = *target.getAddr32(x, y);
- SkPMColor d = 0;
- if (c0 != c1) {
- d = compute_diff_pmcolor(c0, c1);
- }
- *diff->getAddr32(x, y) = d;
- }
- }
-}
-
-static ErrorBitfield compare(const SkBitmap& target, const SkBitmap& base,
- const SkString& name,
- const char* renderModeDescriptor,
- SkBitmap* diff) {
- SkBitmap copy;
- const SkBitmap* bm = &target;
- if (target.config() != SkBitmap::kARGB_8888_Config) {
- target.copyTo(&copy, SkBitmap::kARGB_8888_Config);
- bm = &copy;
- }
- SkBitmap baseCopy;
- const SkBitmap* bp = &base;
- if (base.config() != SkBitmap::kARGB_8888_Config) {
- base.copyTo(&baseCopy, SkBitmap::kARGB_8888_Config);
- bp = &baseCopy;
- }
-
- force_all_opaque(*bm);
- force_all_opaque(*bp);
-
- const int w = bm->width();
- const int h = bm->height();
- if (w != bp->width() || h != bp->height()) {
- SkDebugf(
-"---- %s dimensions mismatch for %s base [%d %d] current [%d %d]\n",
- renderModeDescriptor, name.c_str(),
- bp->width(), bp->height(), w, h);
- return ERROR_DIMENSION_MISMATCH;
- }
-
- SkAutoLockPixels bmLock(*bm);
- SkAutoLockPixels baseLock(*bp);
-
- for (int y = 0; y < h; y++) {
- for (int x = 0; x < w; x++) {
- SkPMColor c0 = *bp->getAddr32(x, y);
- SkPMColor c1 = *bm->getAddr32(x, y);
- if (c0 != c1) {
- SkDebugf(
-"----- %s pixel mismatch for %s at [%d %d] base 0x%08X current 0x%08X\n",
- renderModeDescriptor, name.c_str(), x, y, c0, c1);
-
- if (diff) {
- diff->setConfig(SkBitmap::kARGB_8888_Config, w, h);
- diff->allocPixels();
- compute_diff(*bm, *bp, diff);
- }
- return ERROR_PIXEL_MISMATCH;
- }
- }
- }
-
- // they're equal
- return ERROR_NONE;
-}
-
-static bool write_document(const SkString& path,
- const SkDynamicMemoryWStream& document) {
- SkFILEWStream stream(path.c_str());
- SkAutoDataUnref data(document.copyToData());
- return stream.writeData(data.get());
-}
-
enum Backend {
- kRaster_Backend,
- kGPU_Backend,
- kPDF_Backend,
- kXPS_Backend,
+ kRaster_Backend,
+ kGPU_Backend,
+ kPDF_Backend,
+ kXPS_Backend,
+};
+
+enum BbhType {
+ kNone_BbhType,
+ kRTree_BbhType,
+ kTileGrid_BbhType,
};
enum ConfigFlags {
kNone_ConfigFlag = 0x0,
/* Write GM images if a write path is provided. */
kWrite_ConfigFlag = 0x1,
- /* Read comparison GM images if a read path is provided. */
+ /* Read reference GM images if a read path is provided. */
kRead_ConfigFlag = 0x2,
kRW_ConfigFlag = (kWrite_ConfigFlag | kRead_ConfigFlag),
};
@@ -256,470 +173,676 @@ struct ConfigData {
const char* fName;
};
-/// Returns true if processing should continue, false to skip the
-/// remainder of this config for this GM.
-//@todo thudson 22 April 2011 - could refactor this to take in
-// a factory to generate the context, always call readPixels()
-// (logically a noop for rasters, if wasted time), and thus collapse the
-// GPU special case and also let this be used for SkPicture testing.
-static void setup_bitmap(const ConfigData& gRec, SkISize& size,
- SkBitmap* bitmap) {
- bitmap->setConfig(gRec.fConfig, size.width(), size.height());
- bitmap->allocPixels();
- bitmap->eraseColor(0);
-}
-
-#include "SkDrawFilter.h"
class BWTextDrawFilter : public SkDrawFilter {
public:
- virtual void filter(SkPaint*, Type) SK_OVERRIDE;
+ virtual bool filter(SkPaint*, Type) SK_OVERRIDE;
};
-void BWTextDrawFilter::filter(SkPaint* p, Type t) {
+bool BWTextDrawFilter::filter(SkPaint* p, Type t) {
if (kText_Type == t) {
p->setAntiAlias(false);
}
+ return true;
}
-static void installFilter(SkCanvas* canvas) {
- if (gForceBWtext) {
- canvas->setDrawFilter(new BWTextDrawFilter)->unref();
- }
-}
+struct PipeFlagComboData {
+ const char* name;
+ uint32_t flags;
+};
-static void invokeGM(GM* gm, SkCanvas* canvas, bool isPDF = false) {
- SkAutoCanvasRestore acr(canvas, true);
+static PipeFlagComboData gPipeWritingFlagCombos[] = {
+ { "", 0 },
+ { " cross-process", SkGPipeWriter::kCrossProcess_Flag },
+ { " cross-process, shared address", SkGPipeWriter::kCrossProcess_Flag
+ | SkGPipeWriter::kSharedAddressSpace_Flag }
+};
- if (!isPDF) {
- canvas->concat(gm->getInitialTransform());
+class GMMain {
+public:
+ GMMain() {
+ // Set default values of member variables, which tool_main()
+ // may override.
+ fNotifyMissingReadReference = true;
+ fUseFileHierarchy = false;
}
- installFilter(canvas);
- gm->draw(canvas);
- canvas->setDrawFilter(NULL);
-}
-
-static ErrorBitfield generate_image(GM* gm, const ConfigData& gRec,
- GrContext* context,
- GrRenderTarget* rt,
- SkBitmap* bitmap,
- bool deferred) {
- SkISize size (gm->getISize());
- setup_bitmap(gRec, size, bitmap);
- SkAutoTUnref<SkCanvas> canvas;
-
- if (gRec.fBackend == kRaster_Backend) {
- SkAutoTUnref<SkDevice> device(new SkDevice(*bitmap));
- if (deferred) {
- canvas.reset(new SkDeferredCanvas(device));
+ SkString make_name(const char shortName[], const char configName[]) {
+ SkString name;
+ if (0 == strlen(configName)) {
+ name.append(shortName);
+ } else if (fUseFileHierarchy) {
+ name.appendf("%s%c%s", configName, SkPATH_SEPARATOR, shortName);
} else {
- canvas.reset(new SkCanvas(device));
+ name.appendf("%s_%s", shortName, configName);
}
- invokeGM(gm, canvas);
- canvas->flush();
+ return name;
}
-#if SK_SUPPORT_GPU
- else { // GPU
- if (NULL == context) {
- return ERROR_NO_GPU_CONTEXT;
+
+ static SkString make_filename(const char path[],
+ const char pathSuffix[],
+ const SkString& name,
+ const char suffix[]) {
+ SkString filename(path);
+ if (filename.endsWith(SkPATH_SEPARATOR)) {
+ filename.remove(filename.size() - 1, 1);
}
- SkAutoTUnref<SkDevice> device(new SkGpuDevice(context, rt));
- if (deferred) {
- canvas.reset(new SkDeferredCanvas(device));
- } else {
- canvas.reset(new SkCanvas(device));
+ filename.appendf("%s%c%s.%s", pathSuffix, SkPATH_SEPARATOR,
+ name.c_str(), suffix);
+ return filename;
+ }
+
+ /* since PNG insists on unpremultiplying our alpha, we take no
+ precision chances and force all pixels to be 100% opaque,
+ otherwise on compare we may not get a perfect match.
+ */
+ static void force_all_opaque(const SkBitmap& bitmap) {
+ SkAutoLockPixels lock(bitmap);
+ for (int y = 0; y < bitmap.height(); y++) {
+ for (int x = 0; x < bitmap.width(); x++) {
+ *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
+ }
}
- invokeGM(gm, canvas);
- // the device is as large as the current rendertarget, so we explicitly
- // only readback the amount we expect (in size)
- // overwrite our previous allocation
- bitmap->setConfig(SkBitmap::kARGB_8888_Config, size.fWidth,
- size.fHeight);
- canvas->readPixels(bitmap, 0, 0);
}
-#endif
- return ERROR_NONE;
-}
-static void generate_image_from_picture(GM* gm, const ConfigData& gRec,
- SkPicture* pict, SkBitmap* bitmap) {
- SkISize size = gm->getISize();
- setup_bitmap(gRec, size, bitmap);
- SkCanvas canvas(*bitmap);
- installFilter(&canvas);
- canvas.drawPicture(*pict);
-}
+ static bool write_bitmap(const SkString& path, const SkBitmap& bitmap) {
+ SkBitmap copy;
+ bitmap.copyTo(&copy, SkBitmap::kARGB_8888_Config);
+ force_all_opaque(copy);
+ return SkImageEncoder::EncodeFile(path.c_str(), copy,
+ SkImageEncoder::kPNG_Type, 100);
+ }
-static void generate_pdf(GM* gm, SkDynamicMemoryWStream& pdf) {
-#ifdef SK_SUPPORT_PDF
- SkMatrix initialTransform = gm->getInitialTransform();
- SkISize pageSize = gm->getISize();
- SkPDFDevice* dev = NULL;
- if (initialTransform.isIdentity()) {
- dev = new SkPDFDevice(pageSize, pageSize, initialTransform);
- } else {
- SkRect content = SkRect::MakeWH(SkIntToScalar(pageSize.width()),
- SkIntToScalar(pageSize.height()));
- initialTransform.mapRect(&content);
- content.intersect(0, 0, SkIntToScalar(pageSize.width()),
- SkIntToScalar(pageSize.height()));
- SkISize contentSize =
- SkISize::Make(SkScalarRoundToInt(content.width()),
- SkScalarRoundToInt(content.height()));
- dev = new SkPDFDevice(pageSize, contentSize, initialTransform);
+ static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
+ int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
+ int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
+ int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
+ return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
}
- SkAutoUnref aur(dev);
- SkCanvas c(dev);
- invokeGM(gm, &c, true);
+ static void compute_diff(const SkBitmap& target, const SkBitmap& base,
+ SkBitmap* diff) {
+ SkAutoLockPixels alp(*diff);
+
+ const int w = target.width();
+ const int h = target.height();
+ for (int y = 0; y < h; y++) {
+ for (int x = 0; x < w; x++) {
+ SkPMColor c0 = *base.getAddr32(x, y);
+ SkPMColor c1 = *target.getAddr32(x, y);
+ SkPMColor d = 0;
+ if (c0 != c1) {
+ d = compute_diff_pmcolor(c0, c1);
+ }
+ *diff->getAddr32(x, y) = d;
+ }
+ }
+ }
- SkPDFDocument doc;
- doc.appendPage(dev);
- doc.emitPDF(&pdf);
-#endif
-}
+ // Records an error in fFailedTests, if we want to record errors
+ // of this type.
+ void RecordError(ErrorBitfield errorType, const SkString& name,
+ const char renderModeDescriptor [], int maxPixelError=0) {
+ switch (errorType) {
+ case ERROR_NONE:
+ break;
+ case ERROR_READING_REFERENCE_IMAGE:
+ break;
+ default:
+ FailRec& rec = fFailedTests.push_back(make_name(
+ name.c_str(), renderModeDescriptor));
+ rec.fMaxPixelError = maxPixelError;
+ break;
+ }
+ }
-static void generate_xps(GM* gm, SkDynamicMemoryWStream& xps) {
-#ifdef SK_SUPPORT_XPS
- SkISize size = gm->getISize();
-
- SkSize trimSize = SkSize::Make(SkIntToScalar(size.width()),
- SkIntToScalar(size.height()));
- static const SkScalar inchesPerMeter = SkScalarDiv(10000, 254);
- static const SkScalar upm = 72 * inchesPerMeter;
- SkVector unitsPerMeter = SkPoint::Make(upm, upm);
- static const SkScalar ppm = 200 * inchesPerMeter;
- SkVector pixelsPerMeter = SkPoint::Make(ppm, ppm);
-
- SkXPSDevice* dev = new SkXPSDevice();
- SkAutoUnref aur(dev);
-
- SkCanvas c(dev);
- dev->beginPortfolio(&xps);
- dev->beginSheet(unitsPerMeter, pixelsPerMeter, trimSize);
- invokeGM(gm, &c);
- dev->endSheet();
- dev->endPortfolio();
+ // List contents of fFailedTests via SkDebug.
+ void ListErrors() {
+ for (int i = 0; i < fFailedTests.count(); ++i) {
+ int pixErr = fFailedTests[i].fMaxPixelError;
+ SkString pixStr;
+ if (pixErr > 0) {
+ pixStr.printf(" pixel_error %d", pixErr);
+ }
+ SkDebugf("\t\t%s%s\n", fFailedTests[i].fName.c_str(),
+ pixStr.c_str());
+ }
+ }
-#endif
-}
+ // Compares "target" and "base" bitmaps, returning the result
+ // (ERROR_NONE if the two bitmaps are identical).
+ //
+ // If a "diff" bitmap is passed in, pixel diffs (if any) will be written
+ // into it.
+ ErrorBitfield compare(const SkBitmap& target, const SkBitmap& base,
+ const SkString& name,
+ const char* renderModeDescriptor,
+ SkBitmap* diff) {
+ SkBitmap copy;
+ const SkBitmap* bm = &target;
+ if (target.config() != SkBitmap::kARGB_8888_Config) {
+ target.copyTo(&copy, SkBitmap::kARGB_8888_Config);
+ bm = &copy;
+ }
+ SkBitmap baseCopy;
+ const SkBitmap* bp = &base;
+ if (base.config() != SkBitmap::kARGB_8888_Config) {
+ base.copyTo(&baseCopy, SkBitmap::kARGB_8888_Config);
+ bp = &baseCopy;
+ }
-static ErrorBitfield write_reference_image(const ConfigData& gRec,
- const char writePath [],
- const char renderModeDescriptor [],
- const SkString& name,
- SkBitmap& bitmap,
- SkDynamicMemoryWStream* document) {
- SkString path;
- bool success = false;
- if (gRec.fBackend == kRaster_Backend ||
- gRec.fBackend == kGPU_Backend ||
- (gRec.fBackend == kPDF_Backend && CAN_IMAGE_PDF)) {
-
- path = make_filename(writePath, renderModeDescriptor, name, "png");
- success = write_bitmap(path, bitmap);
- }
- if (kPDF_Backend == gRec.fBackend) {
- path = make_filename(writePath, renderModeDescriptor, name, "pdf");
- success = write_document(path, *document);
+ force_all_opaque(*bm);
+ force_all_opaque(*bp);
+
+ const int w = bm->width();
+ const int h = bm->height();
+ if (w != bp->width() || h != bp->height()) {
+ SkDebugf(
+ "---- %s dimensions mismatch for %s base [%d %d] current [%d %d]\n",
+ renderModeDescriptor, name.c_str(),
+ bp->width(), bp->height(), w, h);
+ RecordError(ERROR_DIMENSION_MISMATCH, name, renderModeDescriptor);
+ return ERROR_DIMENSION_MISMATCH;
+ }
+
+ SkAutoLockPixels bmLock(*bm);
+ SkAutoLockPixels baseLock(*bp);
+
+ int maxErr = 0;
+ for (int y = 0; y < h; y++) {
+ for (int x = 0; x < w; x++) {
+ SkPMColor c0 = *bp->getAddr32(x, y);
+ SkPMColor c1 = *bm->getAddr32(x, y);
+ if (c0 != c1) {
+ maxErr = SkMax32(maxErr, compute_PMColor_maxDiff(c0, c1));
+ }
+ }
+ }
+
+ if (maxErr > 0) {
+ SkDebugf(
+ "----- %s max pixel mismatch for %s is %d\n",
+ renderModeDescriptor, name.c_str(), maxErr);
+ if (diff) {
+ diff->setConfig(SkBitmap::kARGB_8888_Config, w, h);
+ diff->allocPixels();
+ compute_diff(*bm, *bp, diff);
+ }
+ RecordError(ERROR_PIXEL_MISMATCH, name, renderModeDescriptor,
+ maxErr);
+ return ERROR_PIXEL_MISMATCH;
+ }
+ return ERROR_NONE;
}
- if (kXPS_Backend == gRec.fBackend) {
- path = make_filename(writePath, renderModeDescriptor, name, "xps");
- success = write_document(path, *document);
+
+ static bool write_document(const SkString& path,
+ const SkDynamicMemoryWStream& document) {
+ SkFILEWStream stream(path.c_str());
+ SkAutoDataUnref data(document.copyToData());
+ return stream.writeData(data.get());
}
- if (success) {
- return ERROR_NONE;
- } else {
- fprintf(stderr, "FAILED to write %s\n", path.c_str());
- return ERROR_WRITING_REFERENCE_IMAGE;
+
+ /// Returns true if processing should continue, false to skip the
+ /// remainder of this config for this GM.
+ //@todo thudson 22 April 2011 - could refactor this to take in
+ // a factory to generate the context, always call readPixels()
+ // (logically a noop for rasters, if wasted time), and thus collapse the
+ // GPU special case and also let this be used for SkPicture testing.
+ static void setup_bitmap(const ConfigData& gRec, SkISize& size,
+ SkBitmap* bitmap) {
+ bitmap->setConfig(gRec.fConfig, size.width(), size.height());
+ bitmap->allocPixels();
+ bitmap->eraseColor(SK_ColorTRANSPARENT);
}
-}
-static ErrorBitfield compare_to_reference_image(const SkString& name,
- SkBitmap &bitmap,
- const SkBitmap& comparisonBitmap,
- const char diffPath [],
- const char renderModeDescriptor []) {
- ErrorBitfield errors;
- SkBitmap diffBitmap;
- errors = compare(bitmap, comparisonBitmap, name, renderModeDescriptor,
- diffPath ? &diffBitmap : NULL);
- if ((ERROR_NONE != errors) && diffPath) {
- // write out the generated image
- SkString genName = make_filename(diffPath, "", name, "png");
- if (!write_bitmap(genName, bitmap)) {
- errors |= ERROR_WRITING_REFERENCE_IMAGE;
+ static void installFilter(SkCanvas* canvas) {
+ if (gForceBWtext) {
+ canvas->setDrawFilter(new BWTextDrawFilter)->unref();
}
}
- return errors;
-}
-static ErrorBitfield compare_to_reference_image(const char readPath [],
- const SkString& name,
- SkBitmap &bitmap,
- const char diffPath [],
- const char renderModeDescriptor []) {
- SkString path = make_filename(readPath, "", name, "png");
- SkBitmap orig;
- if (SkImageDecoder::DecodeFile(path.c_str(), &orig,
- SkBitmap::kARGB_8888_Config,
- SkImageDecoder::kDecodePixels_Mode, NULL)) {
- return compare_to_reference_image(name, bitmap,
- orig, diffPath,
- renderModeDescriptor);
- } else {
- if (gNotifyMissingReadReference) {
- fprintf(stderr, "FAILED to read %s\n", path.c_str());
+ static void invokeGM(GM* gm, SkCanvas* canvas, bool isPDF, bool isDeferred) {
+ SkAutoCanvasRestore acr(canvas, true);
+
+ if (!isPDF) {
+ canvas->concat(gm->getInitialTransform());
}
- return ERROR_READING_REFERENCE_IMAGE;
+ installFilter(canvas);
+ gm->setCanvasIsDeferred(isDeferred);
+ gm->draw(canvas);
+ canvas->setDrawFilter(NULL);
}
-}
-static ErrorBitfield handle_test_results(GM* gm,
- const ConfigData& gRec,
- const char writePath [],
- const char readPath [],
- const char diffPath [],
- const char renderModeDescriptor [],
- SkBitmap& bitmap,
- SkDynamicMemoryWStream* pdf,
- const SkBitmap* comparisonBitmap) {
- SkString name = make_name(gm->shortName(), gRec.fName);
- ErrorBitfield retval = ERROR_NONE;
-
- if (readPath && (gRec.fFlags & kRead_ConfigFlag)) {
- retval |= compare_to_reference_image(readPath, name, bitmap,
- diffPath, renderModeDescriptor);
+ static ErrorBitfield generate_image(GM* gm, const ConfigData& gRec,
+ GrContext* context,
+ GrRenderTarget* rt,
+ SkBitmap* bitmap,
+ bool deferred) {
+ SkISize size (gm->getISize());
+ setup_bitmap(gRec, size, bitmap);
+
+ SkAutoTUnref<SkCanvas> canvas;
+
+ if (gRec.fBackend == kRaster_Backend) {
+ SkAutoTUnref<SkDevice> device(new SkDevice(*bitmap));
+ if (deferred) {
+ canvas.reset(new SkDeferredCanvas(device));
+ } else {
+ canvas.reset(new SkCanvas(device));
+ }
+ invokeGM(gm, canvas, false, deferred);
+ canvas->flush();
+ }
+#if SK_SUPPORT_GPU
+ else { // GPU
+ if (NULL == context) {
+ return ERROR_NO_GPU_CONTEXT;
+ }
+ SkAutoTUnref<SkDevice> device(new SkGpuDevice(context, rt));
+ if (deferred) {
+ canvas.reset(new SkDeferredCanvas(device));
+ } else {
+ canvas.reset(new SkCanvas(device));
+ }
+ invokeGM(gm, canvas, false, deferred);
+ // the device is as large as the current rendertarget, so
+ // we explicitly only readback the amount we expect (in
+ // size) overwrite our previous allocation
+ bitmap->setConfig(SkBitmap::kARGB_8888_Config, size.fWidth,
+ size.fHeight);
+ canvas->readPixels(bitmap, 0, 0);
+ }
+#endif
+ return ERROR_NONE;
}
- if (writePath && (gRec.fFlags & kWrite_ConfigFlag)) {
- retval |= write_reference_image(gRec, writePath, renderModeDescriptor,
- name, bitmap, pdf);
+
+ static void generate_image_from_picture(GM* gm, const ConfigData& gRec,
+ SkPicture* pict, SkBitmap* bitmap) {
+ SkISize size = gm->getISize();
+ setup_bitmap(gRec, size, bitmap);
+ SkCanvas canvas(*bitmap);
+ installFilter(&canvas);
+ canvas.drawPicture(*pict);
}
- if (comparisonBitmap) {
- retval |= compare_to_reference_image(name, bitmap,
- *comparisonBitmap, diffPath,
- renderModeDescriptor);
+
+ static void generate_pdf(GM* gm, SkDynamicMemoryWStream& pdf) {
+#ifdef SK_SUPPORT_PDF
+ SkMatrix initialTransform = gm->getInitialTransform();
+ SkISize pageSize = gm->getISize();
+ SkPDFDevice* dev = NULL;
+ if (initialTransform.isIdentity()) {
+ dev = new SkPDFDevice(pageSize, pageSize, initialTransform);
+ } else {
+ SkRect content = SkRect::MakeWH(SkIntToScalar(pageSize.width()),
+ SkIntToScalar(pageSize.height()));
+ initialTransform.mapRect(&content);
+ content.intersect(0, 0, SkIntToScalar(pageSize.width()),
+ SkIntToScalar(pageSize.height()));
+ SkISize contentSize =
+ SkISize::Make(SkScalarRoundToInt(content.width()),
+ SkScalarRoundToInt(content.height()));
+ dev = new SkPDFDevice(pageSize, contentSize, initialTransform);
+ }
+ SkAutoUnref aur(dev);
+
+ SkCanvas c(dev);
+ invokeGM(gm, &c, true, false);
+
+ SkPDFDocument doc;
+ doc.appendPage(dev);
+ doc.emitPDF(&pdf);
+#endif
}
- return retval;
-}
-static SkPicture* generate_new_picture(GM* gm) {
- // Pictures are refcounted so must be on heap
- SkPicture* pict = new SkPicture;
- SkISize size = gm->getISize();
- SkCanvas* cv = pict->beginRecording(size.width(), size.height());
- invokeGM(gm, cv);
- pict->endRecording();
+ static void generate_xps(GM* gm, SkDynamicMemoryWStream& xps) {
+#ifdef SK_SUPPORT_XPS
+ SkISize size = gm->getISize();
- return pict;
-}
+ SkSize trimSize = SkSize::Make(SkIntToScalar(size.width()),
+ SkIntToScalar(size.height()));
+ static const SkScalar inchesPerMeter = SkScalarDiv(10000, 254);
+ static const SkScalar upm = 72 * inchesPerMeter;
+ SkVector unitsPerMeter = SkPoint::Make(upm, upm);
+ static const SkScalar ppm = 200 * inchesPerMeter;
+ SkVector pixelsPerMeter = SkPoint::Make(ppm, ppm);
-static SkPicture* stream_to_new_picture(const SkPicture& src) {
-
- // To do in-memory commiunications with a stream, we need to:
- // * create a dynamic memory stream
- // * copy it into a buffer
- // * create a read stream from it
- // ?!?!
-
- SkDynamicMemoryWStream storage;
- src.serialize(&storage);
-
- int streamSize = storage.getOffset();
- SkAutoMalloc dstStorage(streamSize);
- void* dst = dstStorage.get();
- //char* dst = new char [streamSize];
- //@todo thudson 22 April 2011 when can we safely delete [] dst?
- storage.copyTo(dst);
- SkMemoryStream pictReadback(dst, streamSize);
- SkPicture* retval = new SkPicture (&pictReadback);
- return retval;
-}
+ SkXPSDevice* dev = new SkXPSDevice();
+ SkAutoUnref aur(dev);
+
+ SkCanvas c(dev);
+ dev->beginPortfolio(&xps);
+ dev->beginSheet(unitsPerMeter, pixelsPerMeter, trimSize);
+ invokeGM(gm, &c, false, false);
+ dev->endSheet();
+ dev->endPortfolio();
-// Test: draw into a bitmap or pdf.
-// Depending on flags, possibly compare to an expected image
-// and possibly output a diff image if it fails to match.
-static ErrorBitfield test_drawing(GM* gm,
- const ConfigData& gRec,
- const char writePath [],
- const char readPath [],
- const char diffPath [],
- GrContext* context,
- GrRenderTarget* rt,
- SkBitmap* bitmap) {
- SkDynamicMemoryWStream document;
-
- if (gRec.fBackend == kRaster_Backend ||
- gRec.fBackend == kGPU_Backend) {
- // Early exit if we can't generate the image.
- ErrorBitfield errors = generate_image(gm, gRec, context, rt, bitmap,
- false);
- if (ERROR_NONE != errors) {
- return errors;
- }
- } else if (gRec.fBackend == kPDF_Backend) {
- generate_pdf(gm, document);
-#if CAN_IMAGE_PDF
- SkAutoDataUnref data(document.copyToData());
- SkMemoryStream stream(data->data(), data->size());
- SkPDFDocumentToBitmap(&stream, bitmap);
#endif
- } else if (gRec.fBackend == kXPS_Backend) {
- generate_xps(gm, document);
}
- return handle_test_results(gm, gRec, writePath, readPath, diffPath,
- "", *bitmap, &document, NULL);
-}
-static ErrorBitfield test_deferred_drawing(GM* gm,
- const ConfigData& gRec,
- const SkBitmap& comparisonBitmap,
- const char diffPath [],
- GrContext* context,
- GrRenderTarget* rt) {
- SkDynamicMemoryWStream document;
-
- if (gRec.fBackend == kRaster_Backend ||
- gRec.fBackend == kGPU_Backend) {
- SkBitmap bitmap;
- // Early exit if we can't generate the image, but this is
- // expected in some cases, so don't report a test failure.
- if (!generate_image(gm, gRec, context, rt, &bitmap, true)) {
+ ErrorBitfield write_reference_image(
+ const ConfigData& gRec, const char writePath [],
+ const char renderModeDescriptor [], const SkString& name,
+ SkBitmap& bitmap, SkDynamicMemoryWStream* document) {
+ SkString path;
+ bool success = false;
+ if (gRec.fBackend == kRaster_Backend ||
+ gRec.fBackend == kGPU_Backend ||
+ (gRec.fBackend == kPDF_Backend && CAN_IMAGE_PDF)) {
+
+ path = make_filename(writePath, renderModeDescriptor, name, "png");
+ success = write_bitmap(path, bitmap);
+ }
+ if (kPDF_Backend == gRec.fBackend) {
+ path = make_filename(writePath, renderModeDescriptor, name, "pdf");
+ success = write_document(path, *document);
+ }
+ if (kXPS_Backend == gRec.fBackend) {
+ path = make_filename(writePath, renderModeDescriptor, name, "xps");
+ success = write_document(path, *document);
+ }
+ if (success) {
return ERROR_NONE;
+ } else {
+ fprintf(stderr, "FAILED to write %s\n", path.c_str());
+ RecordError(ERROR_WRITING_REFERENCE_IMAGE, name,
+ renderModeDescriptor);
+ return ERROR_WRITING_REFERENCE_IMAGE;
}
- return handle_test_results(gm, gRec, NULL, NULL, diffPath,
- "-deferred", bitmap, NULL, &comparisonBitmap);
}
- return ERROR_NONE;
-}
-static ErrorBitfield test_picture_playback(GM* gm,
- const ConfigData& gRec,
- const SkBitmap& comparisonBitmap,
- const char readPath [],
- const char diffPath []) {
- SkPicture* pict = generate_new_picture(gm);
- SkAutoUnref aur(pict);
-
- if (kRaster_Backend == gRec.fBackend) {
- SkBitmap bitmap;
- generate_image_from_picture(gm, gRec, pict, &bitmap);
- return handle_test_results(gm, gRec, NULL, NULL, diffPath,
- "-replay", bitmap, NULL, &comparisonBitmap);
- } else {
- return ERROR_NONE;
+ // Compares bitmap "bitmap" to "referenceBitmap"; if they are
+ // different, writes out "bitmap" (in PNG format) within the
+ // diffPath subdir.
+ //
+ // Returns the ErrorBitfield from compare(), describing any differences
+ // between "bitmap" and "referenceBitmap" (or ERROR_NONE if there are none).
+ ErrorBitfield compare_to_reference_image_in_memory(
+ const SkString& name, SkBitmap &bitmap, const SkBitmap& referenceBitmap,
+ const char diffPath [], const char renderModeDescriptor []) {
+ ErrorBitfield errors;
+ SkBitmap diffBitmap;
+ errors = compare(bitmap, referenceBitmap, name, renderModeDescriptor,
+ diffPath ? &diffBitmap : NULL);
+ if ((ERROR_NONE != errors) && diffPath) {
+ // write out the generated image
+ SkString genName = make_filename(diffPath, "", name, "png");
+ if (!write_bitmap(genName, bitmap)) {
+ RecordError(ERROR_WRITING_REFERENCE_IMAGE, name,
+ renderModeDescriptor);
+ errors |= ERROR_WRITING_REFERENCE_IMAGE;
+ }
+ }
+ return errors;
}
-}
-static ErrorBitfield test_picture_serialization(GM* gm,
- const ConfigData& gRec,
- const SkBitmap& comparisonBitmap,
- const char readPath [],
- const char diffPath []) {
- SkPicture* pict = generate_new_picture(gm);
- SkAutoUnref aurp(pict);
- SkPicture* repict = stream_to_new_picture(*pict);
- SkAutoUnref aurr(repict);
-
- if (kRaster_Backend == gRec.fBackend) {
- SkBitmap bitmap;
- generate_image_from_picture(gm, gRec, repict, &bitmap);
- return handle_test_results(gm, gRec, NULL, NULL, diffPath,
- "-serialize", bitmap, NULL, &comparisonBitmap);
- } else {
- return ERROR_NONE;
- }
-}
+ // Compares bitmap "bitmap" to a reference bitmap read from disk;
+ // if they are different, writes out "bitmap" (in PNG format)
+ // within the diffPath subdir.
+ //
+ // Returns a description of the difference between "bitmap" and
+ // the reference bitmap, or ERROR_READING_REFERENCE_IMAGE if
+ // unable to read the reference bitmap from disk.
+ ErrorBitfield compare_to_reference_image_on_disk(
+ const char readPath [], const SkString& name, SkBitmap &bitmap,
+ const char diffPath [], const char renderModeDescriptor []) {
+ ErrorBitfield retval;
+ SkString path = make_filename(readPath, "", name, "png");
+ SkBitmap referenceBitmap;
+ Json::Value expectedChecksumsArray;
+
+ bool decodedReferenceBitmap =
+ SkImageDecoder::DecodeFile(path.c_str(), &referenceBitmap,
+ SkBitmap::kARGB_8888_Config,
+ SkImageDecoder::kDecodePixels_Mode,
+ NULL);
+ if (decodedReferenceBitmap) {
+ expectedChecksumsArray.append(Json::UInt64(
+ SkBitmapChecksummer::Compute64(referenceBitmap)));
+ retval = compare_to_reference_image_in_memory(name, bitmap,
+ referenceBitmap,
+ diffPath,
+ renderModeDescriptor);
+ } else {
+ if (fNotifyMissingReadReference) {
+ fprintf(stderr, "FAILED to read %s\n", path.c_str());
+ }
+ RecordError(ERROR_READING_REFERENCE_IMAGE, name,
+ renderModeDescriptor);
+ retval = ERROR_READING_REFERENCE_IMAGE;
+ }
-struct PipeFlagComboData {
- const char* name;
- uint32_t flags;
-};
+ // Add this result to the appropriate JSON collection of actual results,
+ // depending on status.
+ Json::Value actualResults;
+ actualResults[kJsonKey_ActualResults_AnyStatus_Checksum] = Json::UInt64(
+ SkBitmapChecksummer::Compute64(bitmap));
+ if (decodedReferenceBitmap) {
+ if (ERROR_NONE == retval) {
+ fJsonActualResults_Succeeded[name.c_str()] = actualResults;
+ } else {
+ fJsonActualResults_Failed[name.c_str()] = actualResults;
+ }
+ } else {
+ fJsonActualResults_FailureIgnored[name.c_str()] = actualResults;
+ }
-static PipeFlagComboData gPipeWritingFlagCombos[] = {
- { "", 0 },
- { " cross-process", SkGPipeWriter::kCrossProcess_Flag },
- { " cross-process, shared address", SkGPipeWriter::kCrossProcess_Flag
- | SkGPipeWriter::kSharedAddressSpace_Flag }
-};
+ // Add this test to the JSON collection of expected results.
+ // For now, we assume that this collection starts out empty and we
+ // just fill it in as we go; once gm accepts a JSON file as input,
+ // we'll have to change that.
+ Json::Value expectedResults;
+ expectedResults[kJsonKey_ExpectedResults_Checksums] = expectedChecksumsArray;
+ expectedResults[kJsonKey_ExpectedResults_IgnoreFailure] = !decodedReferenceBitmap;
+ fJsonExpectedResults[name.c_str()] = expectedResults;
-static ErrorBitfield test_pipe_playback(GM* gm,
- const ConfigData& gRec,
- const SkBitmap& comparisonBitmap,
- const char readPath [],
- const char diffPath []) {
- if (kRaster_Backend != gRec.fBackend) {
- return ERROR_NONE;
+ return retval;
}
- ErrorBitfield errors = ERROR_NONE;
- for (size_t i = 0; i < SK_ARRAY_COUNT(gPipeWritingFlagCombos); ++i) {
- SkBitmap bitmap;
+
+ // NOTE: As far as I can tell, this function is NEVER called with a
+ // non-blank renderModeDescriptor, EXCEPT when readPath and writePath are
+ // both NULL (and thus no images are read from or written to disk).
+ // So I don't trust that the renderModeDescriptor is being used for
+ // anything other than debug output these days.
+ ErrorBitfield handle_test_results(GM* gm,
+ const ConfigData& gRec,
+ const char writePath [],
+ const char readPath [],
+ const char diffPath [],
+ const char renderModeDescriptor [],
+ SkBitmap& bitmap,
+ SkDynamicMemoryWStream* pdf,
+ const SkBitmap* referenceBitmap) {
+ SkString name = make_name(gm->shortName(), gRec.fName);
+ ErrorBitfield retval = ERROR_NONE;
+
+ if (readPath && (gRec.fFlags & kRead_ConfigFlag)) {
+ retval |= compare_to_reference_image_on_disk(readPath, name, bitmap,
+ diffPath,
+ renderModeDescriptor);
+ }
+ if (writePath && (gRec.fFlags & kWrite_ConfigFlag)) {
+ retval |= write_reference_image(gRec, writePath,
+ renderModeDescriptor,
+ name, bitmap, pdf);
+ }
+ if (referenceBitmap) {
+ retval |= compare_to_reference_image_in_memory(
+ name, bitmap, *referenceBitmap, diffPath, renderModeDescriptor);
+ }
+ return retval;
+ }
+
+ static SkPicture* generate_new_picture(GM* gm, BbhType bbhType) {
+ // Pictures are refcounted so must be on heap
+ SkPicture* pict;
SkISize size = gm->getISize();
- setup_bitmap(gRec, size, &bitmap);
- SkCanvas canvas(bitmap);
- PipeController pipeController(&canvas);
- SkGPipeWriter writer;
- SkCanvas* pipeCanvas = writer.startRecording(&pipeController,
- gPipeWritingFlagCombos[i].flags);
- invokeGM(gm, pipeCanvas);
- writer.endRecording();
- SkString string("-pipe");
- string.append(gPipeWritingFlagCombos[i].name);
- errors |= handle_test_results(gm, gRec, NULL, NULL, diffPath,
- string.c_str(), bitmap, NULL, &comparisonBitmap);
- if (errors != ERROR_NONE) {
- break;
+ if (kTileGrid_BbhType == bbhType) {
+ pict = new SkTileGridPicture(16, 16, size.width(), size.height());
+ } else {
+ pict = new SkPicture;
}
+ uint32_t recordFlags = (kNone_BbhType == bbhType) ?
+ 0 : SkPicture::kOptimizeForClippedPlayback_RecordingFlag;
+ SkCanvas* cv = pict->beginRecording(size.width(), size.height(), recordFlags);
+ invokeGM(gm, cv, false, false);
+ pict->endRecording();
+
+ return pict;
+ }
+
+ static SkPicture* stream_to_new_picture(const SkPicture& src) {
+
+ // To do in-memory commiunications with a stream, we need to:
+ // * create a dynamic memory stream
+ // * copy it into a buffer
+ // * create a read stream from it
+ // ?!?!
+
+ SkDynamicMemoryWStream storage;
+ src.serialize(&storage);
+
+ int streamSize = storage.getOffset();
+ SkAutoMalloc dstStorage(streamSize);
+ void* dst = dstStorage.get();
+ //char* dst = new char [streamSize];
+ //@todo thudson 22 April 2011 when can we safely delete [] dst?
+ storage.copyTo(dst);
+ SkMemoryStream pictReadback(dst, streamSize);
+ SkPicture* retval = new SkPicture (&pictReadback);
+ return retval;
}
- return errors;
-}
-static ErrorBitfield test_tiled_pipe_playback(GM* gm,
+ // Test: draw into a bitmap or pdf.
+ // Depending on flags, possibly compare to an expected image
+ // and possibly output a diff image if it fails to match.
+ ErrorBitfield test_drawing(GM* gm,
+ const ConfigData& gRec,
+ const char writePath [],
+ const char readPath [],
+ const char diffPath [],
+ GrContext* context,
+ GrRenderTarget* rt,
+ SkBitmap* bitmap) {
+ SkDynamicMemoryWStream document;
+
+ if (gRec.fBackend == kRaster_Backend ||
+ gRec.fBackend == kGPU_Backend) {
+ // Early exit if we can't generate the image.
+ ErrorBitfield errors = generate_image(gm, gRec, context, rt, bitmap,
+ false);
+ if (ERROR_NONE != errors) {
+ return errors;
+ }
+ } else if (gRec.fBackend == kPDF_Backend) {
+ generate_pdf(gm, document);
+#if CAN_IMAGE_PDF
+ SkAutoDataUnref data(document.copyToData());
+ SkMemoryStream stream(data->data(), data->size());
+ SkPDFDocumentToBitmap(&stream, bitmap);
+#endif
+ } else if (gRec.fBackend == kXPS_Backend) {
+ generate_xps(gm, document);
+ }
+ return handle_test_results(gm, gRec, writePath, readPath, diffPath,
+ "", *bitmap, &document, NULL);
+ }
+
+ ErrorBitfield test_deferred_drawing(GM* gm,
const ConfigData& gRec,
- const SkBitmap& comparisonBitmap,
- const char readPath [],
- const char diffPath []) {
- if (kRaster_Backend != gRec.fBackend) {
+ const SkBitmap& referenceBitmap,
+ const char diffPath [],
+ GrContext* context,
+ GrRenderTarget* rt) {
+ SkDynamicMemoryWStream document;
+
+ if (gRec.fBackend == kRaster_Backend ||
+ gRec.fBackend == kGPU_Backend) {
+ SkBitmap bitmap;
+ // Early exit if we can't generate the image, but this is
+ // expected in some cases, so don't report a test failure.
+ if (!generate_image(gm, gRec, context, rt, &bitmap, true)) {
+ return ERROR_NONE;
+ }
+ return handle_test_results(gm, gRec, NULL, NULL, diffPath,
+ "-deferred", bitmap, NULL,
+ &referenceBitmap);
+ }
return ERROR_NONE;
}
- ErrorBitfield errors = ERROR_NONE;
- for (size_t i = 0; i < SK_ARRAY_COUNT(gPipeWritingFlagCombos); ++i) {
- SkBitmap bitmap;
- SkISize size = gm->getISize();
- setup_bitmap(gRec, size, &bitmap);
- SkCanvas canvas(bitmap);
- TiledPipeController pipeController(bitmap);
- SkGPipeWriter writer;
- SkCanvas* pipeCanvas = writer.startRecording(&pipeController,
- gPipeWritingFlagCombos[i].flags);
- invokeGM(gm, pipeCanvas);
- writer.endRecording();
- SkString string("-tiled pipe");
- string.append(gPipeWritingFlagCombos[i].name);
- errors |= handle_test_results(gm, gRec, NULL, NULL, diffPath,
- string.c_str(), bitmap, NULL, &comparisonBitmap);
- if (errors != ERROR_NONE) {
- break;
+
+ ErrorBitfield test_pipe_playback(GM* gm,
+ const ConfigData& gRec,
+ const SkBitmap& referenceBitmap,
+ const char readPath [],
+ const char diffPath []) {
+ ErrorBitfield errors = ERROR_NONE;
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gPipeWritingFlagCombos); ++i) {
+ SkBitmap bitmap;
+ SkISize size = gm->getISize();
+ setup_bitmap(gRec, size, &bitmap);
+ SkCanvas canvas(bitmap);
+ PipeController pipeController(&canvas);
+ SkGPipeWriter writer;
+ SkCanvas* pipeCanvas = writer.startRecording(
+ &pipeController, gPipeWritingFlagCombos[i].flags);
+ invokeGM(gm, pipeCanvas, false, false);
+ writer.endRecording();
+ SkString string("-pipe");
+ string.append(gPipeWritingFlagCombos[i].name);
+ errors |= handle_test_results(gm, gRec, NULL, NULL, diffPath,
+ string.c_str(), bitmap, NULL,
+ &referenceBitmap);
+ if (errors != ERROR_NONE) {
+ break;
+ }
}
+ return errors;
}
- return errors;
-}
-static void write_picture_serialization(GM* gm, const ConfigData& rec,
- const char writePicturePath[]) {
- // only do this once, so we pick raster
- if (kRaster_Backend == rec.fBackend &&
- SkBitmap::kARGB_8888_Config == rec.fConfig) {
+ ErrorBitfield test_tiled_pipe_playback(
+ GM* gm, const ConfigData& gRec, const SkBitmap& referenceBitmap,
+ const char readPath [], const char diffPath []) {
+ ErrorBitfield errors = ERROR_NONE;
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gPipeWritingFlagCombos); ++i) {
+ SkBitmap bitmap;
+ SkISize size = gm->getISize();
+ setup_bitmap(gRec, size, &bitmap);
+ SkCanvas canvas(bitmap);
+ TiledPipeController pipeController(bitmap);
+ SkGPipeWriter writer;
+ SkCanvas* pipeCanvas = writer.startRecording(
+ &pipeController, gPipeWritingFlagCombos[i].flags);
+ invokeGM(gm, pipeCanvas, false, false);
+ writer.endRecording();
+ SkString string("-tiled pipe");
+ string.append(gPipeWritingFlagCombos[i].name);
+ errors |= handle_test_results(gm, gRec, NULL, NULL, diffPath,
+ string.c_str(), bitmap, NULL,
+ &referenceBitmap);
+ if (errors != ERROR_NONE) {
+ break;
+ }
+ }
+ return errors;
+ }
- SkAutoTUnref<SkPicture> pict(generate_new_picture(gm));
+ //
+ // member variables.
+ // They are public for now, to allow easier setting by tool_main().
+ //
- const char* pictureSuffix = "skp";
- SkString path = make_filename(writePicturePath, "",
- SkString(gm->shortName()), pictureSuffix);
+ // if true, emit a message when we can't find a reference image to compare
+ bool fNotifyMissingReadReference;
- SkFILEWStream stream(path.c_str());
- pict->serialize(&stream);
- }
-}
+ bool fUseFileHierarchy;
+
+ // information about all failed tests we have encountered so far
+ SkTArray<FailRec> fFailedTests;
+
+ Json::Value fJsonExpectedResults;
+ Json::Value fJsonActualResults_Failed;
+ Json::Value fJsonActualResults_FailureIgnored;
+ Json::Value fJsonActualResults_Succeeded;
+
+}; // end of GMMain class definition
#if SK_SUPPORT_GPU
static const GLContextType kDontCare_GLContextType = GrContextFactory::kNative_GLContextType;
@@ -728,13 +851,15 @@ static const GLContextType kDontCare_GLContextType = 0;
#endif
// If the platform does not support writing PNGs of PDFs then there will be no
-// comparison images to read. However, we can always write the .pdf files
+// reference images to read. However, we can always write the .pdf files
static const ConfigFlags kPDFConfigFlags = CAN_IMAGE_PDF ? kRW_ConfigFlag :
kWrite_ConfigFlag;
static const ConfigData gRec[] = {
{ SkBitmap::kARGB_8888_Config, kRaster_Backend, kDontCare_GLContextType, 0, kRW_ConfigFlag, "8888" },
+#if 0 // stop testing this (for now at least) since we want to remove support for it (soon please!!!)
{ SkBitmap::kARGB_4444_Config, kRaster_Backend, kDontCare_GLContextType, 0, kRW_ConfigFlag, "4444" },
+#endif
{ SkBitmap::kRGB_565_Config, kRaster_Backend, kDontCare_GLContextType, 0, kRW_ConfigFlag, "565" },
#if defined(SK_SCALAR_IS_FLOAT) && SK_SUPPORT_GPU
{ SkBitmap::kARGB_8888_Config, kGPU_Backend, GrContextFactory::kNative_GLContextType, 0, kRW_ConfigFlag, "gpu" },
@@ -763,8 +888,6 @@ static const ConfigData gRec[] = {
static void usage(const char * argv0) {
SkDebugf("%s\n", argv0);
- SkDebugf(" [-w writePath] [-r readPath] [-d diffPath] [-i resourcePath]\n");
- SkDebugf(" [-wp writePicturePath]\n");
SkDebugf(" [--config ");
for (size_t i = 0; i < SK_ARRAY_COUNT(gRec); ++i) {
if (i > 0) {
@@ -772,30 +895,40 @@ static void usage(const char * argv0) {
}
SkDebugf(gRec[i].fName);
}
- SkDebugf(" ]\n");
- SkDebugf(" [--noreplay] [--nopipe] [--noserialize] [--forceBWtext] [--nopdf] \n"
- " [--tiledPipe] \n"
- " [--nodeferred] [--match substring] [--notexturecache]\n"
- " [-h|--help]\n"
- );
- SkDebugf(" writePath: directory to write rendered images in.\n");
- SkDebugf(" writePicturePath: directory to write images to in .skp format.\n");
- SkDebugf(
- " readPath: directory to read reference images from;\n"
- " reports if any pixels mismatch between reference and new images\n");
- SkDebugf(" diffPath: directory to write difference images in.\n");
- SkDebugf(" resourcePath: directory that stores image resources.\n");
- SkDebugf(" --noreplay: do not exercise SkPicture replay.\n");
- SkDebugf(" --nopipe: Skip SkGPipe replay.\n");
- SkDebugf(" --tiledPipe: Exercise tiled SkGPipe replay.\n");
+ SkDebugf("]:\n run these configurations\n");
SkDebugf(
- " --noserialize: do not exercise SkPicture serialization & deserialization.\n");
- SkDebugf(" --forceBWtext: disable text anti-aliasing.\n");
- SkDebugf(" --nopdf: skip the pdf rendering test pass.\n");
- SkDebugf(" --nodeferred: skip the deferred rendering test pass.\n");
- SkDebugf(" --match foo: will only run tests that substring match foo.\n");
- SkDebugf(" --notexturecache: disable the gpu texture cache.\n");
- SkDebugf(" -h|--help : Show this help message. \n");
+// Alphabetized ignoring "no" prefix ("readPath", "noreplay", "resourcePath").
+// It would probably be better if we allowed both yes-and-no settings for each
+// one, e.g.:
+// [--replay|--noreplay]: whether to exercise SkPicture replay; default is yes
+" [--nodeferred]: skip the deferred rendering test pass\n"
+" [--diffPath|-d <path>]: write difference images into this directory\n"
+" [--disable-missing-warning]: don't print a message to stderr if\n"
+" unable to read a reference image for any tests (NOT default behavior)\n"
+" [--enable-missing-warning]: print message to stderr (but don't fail) if\n"
+" unable to read a reference image for any tests (default behavior)\n"
+" [--forceBWtext]: disable text anti-aliasing\n"
+" [--help|-h]: show this help message\n"
+" [--hierarchy|--nohierarchy]: whether to use multilevel directory structure\n"
+" when reading/writing files; default is no\n"
+" [--match <substring>]: only run tests whose name includes this substring\n"
+" [--modulo <remainder> <divisor>]: only run tests for which \n"
+" testIndex %% divisor == remainder\n"
+" [--nopdf]: skip the pdf rendering test pass\n"
+" [--nopipe]: Skip SkGPipe replay\n"
+" [--readPath|-r <path>]: read reference images from this dir, and report\n"
+" any differences between those and the newly generated ones\n"
+" [--noreplay]: do not exercise SkPicture replay\n"
+" [--resourcePath|-i <path>]: directory that stores image resources\n"
+" [--nortree]: Do not exercise the R-Tree variant of SkPicture\n"
+" [--noserialize]: do not exercise SkPicture serialization & deserialization\n"
+" [--notexturecache]: disable the gpu texture cache\n"
+" [--tiledPipe]: Exercise tiled SkGPipe replay\n"
+" [--notileGrid]: Do not exercise the tile grid variant of SkPicture\n"
+" [--writeJsonSummary <path>]: write a JSON-formatted result summary to this file\n"
+" [--writePath|-w <path>]: write rendered images into this directory\n"
+" [--writePicturePath|-wp <path>]: write .skp files into this directory\n"
+ );
}
static int findConfig(const char config[]) {
@@ -825,7 +958,7 @@ namespace skiagm {
#if SK_SUPPORT_GPU
SkAutoTUnref<GrContext> gGrContext;
/**
- * Sets the global GrContext, accessible by indivual GMs
+ * Sets the global GrContext, accessible by individual GMs
*/
static void SetGr(GrContext* grContext) {
SkSafeRef(grContext);
@@ -874,7 +1007,9 @@ int tool_main(int argc, char** argv) {
gSkSuppressFontCachePurgeSpew = true;
setSystemPreferences();
+ GMMain gmmain;
+ const char* writeJsonSummaryPath = NULL;// if non-null, where we write the JSON summary
const char* writePath = NULL; // if non-null, where we write the originals
const char* writePicturePath = NULL; // if non-null, where we write serialized pictures
const char* readPath = NULL; // if non-null, were we read from to compare
@@ -889,92 +1024,127 @@ int tool_main(int argc, char** argv) {
bool doTiledPipe = false;
bool doSerialize = true;
bool doDeferred = true;
+ bool doRTree = true;
+ bool doTileGrid = true;
bool disableTextureCache = false;
SkTDArray<size_t> configs;
bool userConfig = false;
- gNotifyMissingReadReference = true;
+ int moduloRemainder = -1;
+ int moduloDivisor = -1;
const char* const commandName = argv[0];
char* const* stop = argv + argc;
for (++argv; argv < stop; ++argv) {
- if (strcmp(*argv, "-w") == 0) {
+ if (strcmp(*argv, "--config") == 0) {
argv++;
- if (argv < stop && **argv) {
- writePath = *argv;
+ if (argv < stop) {
+ int index = findConfig(*argv);
+ if (index >= 0) {
+ *configs.append() = index;
+ userConfig = true;
+ } else {
+ SkString str;
+ str.printf("unrecognized config %s\n", *argv);
+ SkDebugf(str.c_str());
+ usage(commandName);
+ return -1;
+ }
+ } else {
+ SkDebugf("missing arg for --config\n");
+ usage(commandName);
+ return -1;
}
- } else if (strcmp(*argv, "-wp") == 0) {
+ } else if (strcmp(*argv, "--nodeferred") == 0) {
+ doDeferred = false;
+ } else if ((0 == strcmp(*argv, "--diffPath")) ||
+ (0 == strcmp(*argv, "-d"))) {
argv++;
if (argv < stop && **argv) {
- writePicturePath = *argv;
+ diffPath = *argv;
}
- } else if (strcmp(*argv, "-r") == 0) {
- argv++;
+ } else if (strcmp(*argv, "--disable-missing-warning") == 0) {
+ gmmain.fNotifyMissingReadReference = false;
+ } else if (strcmp(*argv, "--nortree") == 0) {
+ doRTree = false;
+ } else if (strcmp(*argv, "--notileGrid") == 0) {
+ doTileGrid = false;
+ } else if (strcmp(*argv, "--enable-missing-warning") == 0) {
+ gmmain.fNotifyMissingReadReference = true;
+ } else if (strcmp(*argv, "--forceBWtext") == 0) {
+ gForceBWtext = true;
+ } else if (strcmp(*argv, "--help") == 0 || strcmp(*argv, "-h") == 0) {
+ usage(commandName);
+ return -1;
+ } else if (strcmp(*argv, "--hierarchy") == 0) {
+ gmmain.fUseFileHierarchy = true;
+ } else if (strcmp(*argv, "--nohierarchy") == 0) {
+ gmmain.fUseFileHierarchy = false;
+ } else if (strcmp(*argv, "--match") == 0) {
+ ++argv;
if (argv < stop && **argv) {
- readPath = *argv;
+ // just record the ptr, no need for a deep copy
+ *fMatches.append() = *argv;
+ }
+ } else if (strcmp(*argv, "--modulo") == 0) {
+ ++argv;
+ if (argv >= stop) {
+ continue;
+ }
+ moduloRemainder = atoi(*argv);
+
+ ++argv;
+ if (argv >= stop) {
+ continue;
}
- } else if (strcmp(*argv, "-d") == 0) {
+ moduloDivisor = atoi(*argv);
+ if (moduloRemainder < 0 || moduloDivisor <= 0 || moduloRemainder >= moduloDivisor) {
+ SkDebugf("invalid modulo values.");
+ return -1;
+ }
+ } else if (strcmp(*argv, "--nopdf") == 0) {
+ doPDF = false;
+ } else if (strcmp(*argv, "--nopipe") == 0) {
+ doPipe = false;
+ } else if ((0 == strcmp(*argv, "--readPath")) ||
+ (0 == strcmp(*argv, "-r"))) {
argv++;
if (argv < stop && **argv) {
- diffPath = *argv;
+ readPath = *argv;
}
- } else if (strcmp(*argv, "-i") == 0) {
+ } else if (strcmp(*argv, "--noreplay") == 0) {
+ doReplay = false;
+ } else if ((0 == strcmp(*argv, "--resourcePath")) ||
+ (0 == strcmp(*argv, "-i"))) {
argv++;
if (argv < stop && **argv) {
resourcePath = *argv;
}
- } else if (strcmp(*argv, "--forceBWtext") == 0) {
- gForceBWtext = true;
- } else if (strcmp(*argv, "--nopipe") == 0) {
- doPipe = false;
- } else if (strcmp(*argv, "--tiledPipe") == 0) {
- doTiledPipe = true;
- } else if (strcmp(*argv, "--noreplay") == 0) {
- doReplay = false;
- } else if (strcmp(*argv, "--nopdf") == 0) {
- doPDF = false;
- } else if (strcmp(*argv, "--nodeferred") == 0) {
- doDeferred = false;
- } else if (strcmp(*argv, "--disable-missing-warning") == 0) {
- gNotifyMissingReadReference = false;
- } else if (strcmp(*argv, "--enable-missing-warning") == 0) {
- gNotifyMissingReadReference = true;
} else if (strcmp(*argv, "--serialize") == 0) {
- // Leaving in this option so that a user need not modify their command line arguments
- // to still run.
doSerialize = true;
} else if (strcmp(*argv, "--noserialize") == 0) {
doSerialize = false;
- } else if (strcmp(*argv, "--match") == 0) {
- ++argv;
- if (argv < stop && **argv) {
- // just record the ptr, no need for a deep copy
- *fMatches.append() = *argv;
- }
} else if (strcmp(*argv, "--notexturecache") == 0) {
disableTextureCache = true;
- } else if (strcmp(*argv, "--config") == 0) {
+ } else if (strcmp(*argv, "--tiledPipe") == 0) {
+ doTiledPipe = true;
+ } else if ((0 == strcmp(*argv, "--writePath")) ||
+ (0 == strcmp(*argv, "-w"))) {
argv++;
- if (argv < stop) {
- int index = findConfig(*argv);
- if (index >= 0) {
- *configs.append() = index;
- userConfig = true;
- } else {
- SkString str;
- str.printf("unrecognized config %s\n", *argv);
- SkDebugf(str.c_str());
- usage(commandName);
- return -1;
- }
- } else {
- SkDebugf("missing arg for --config\n");
- usage(commandName);
- return -1;
+ if (argv < stop && **argv) {
+ writePath = *argv;
+ }
+ } else if (0 == strcmp(*argv, "--writeJsonSummary")) {
+ argv++;
+ if (argv < stop && **argv) {
+ writeJsonSummaryPath = *argv;
+ }
+ } else if ((0 == strcmp(*argv, "--writePicturePath")) ||
+ (0 == strcmp(*argv, "-wp"))) {
+ argv++;
+ if (argv < stop && **argv) {
+ writePicturePath = *argv;
}
- } else if (strcmp(*argv, "--help") == 0 || strcmp(*argv, "-h") == 0) {
- usage(commandName);
- return -1;
} else {
usage(commandName);
return -1;
@@ -1007,6 +1177,13 @@ int tool_main(int argc, char** argv) {
fprintf(stderr, "reading resources from %s\n", resourcePath);
}
+ if (moduloDivisor <= 0) {
+ moduloRemainder = -1;
+ }
+ if (moduloRemainder < 0 || moduloRemainder >= moduloDivisor) {
+ moduloRemainder = -1;
+ }
+
// Accumulate success of all tests.
int testsRun = 0;
int testsPassed = 0;
@@ -1020,11 +1197,39 @@ int tool_main(int argc, char** argv) {
}
#endif
- SkTArray<SkString> failedTests;
+ int gmIndex = -1;
+ SkString moduloStr;
+
+ // If we will be writing out files, prepare subdirectories.
+ if (writePath) {
+ if (!sk_mkdir(writePath)) {
+ return -1;
+ }
+ if (gmmain.fUseFileHierarchy) {
+ for (int i = 0; i < configs.count(); i++) {
+ ConfigData config = gRec[configs[i]];
+ SkString subdir;
+ subdir.appendf("%s%c%s", writePath, SkPATH_SEPARATOR,
+ config.fName);
+ if (!sk_mkdir(subdir.c_str())) {
+ return -1;
+ }
+ }
+ }
+ }
Iter iter;
GM* gm;
while ((gm = iter.next()) != NULL) {
+
+ ++gmIndex;
+ if (moduloRemainder >= 0) {
+ if ((gmIndex % moduloDivisor) != moduloRemainder) {
+ continue;
+ }
+ moduloStr.printf("[%d.%d] ", gmIndex, moduloDivisor);
+ }
+
const char* shortName = gm->shortName();
if (skip_name(fMatches, shortName)) {
SkDELETE(gm);
@@ -1032,19 +1237,21 @@ int tool_main(int argc, char** argv) {
}
SkISize size = gm->getISize();
- SkDebugf("drawing... %s [%d %d]\n", shortName,
+ SkDebugf("%sdrawing... %s [%d %d]\n", moduloStr.c_str(), shortName,
size.width(), size.height());
- SkBitmap forwardRenderedBitmap;
+
+ ErrorBitfield testErrors = ERROR_NONE;
+ uint32_t gmFlags = gm->getFlags();
for (int i = 0; i < configs.count(); i++) {
ConfigData config = gRec[configs[i]];
+
// Skip any tests that we don't even need to try.
- uint32_t gmFlags = gm->getFlags();
if ((kPDF_Backend == config.fBackend) &&
(!doPDF || (gmFlags & GM::kSkipPDF_Flag)))
- {
- continue;
- }
+ {
+ continue;
+ }
if ((gmFlags & GM::kSkip565_Flag) &&
(kRaster_Backend == config.fBackend) &&
(SkBitmap::kRGB_565_Config == config.fConfig)) {
@@ -1053,12 +1260,12 @@ int tool_main(int argc, char** argv) {
// Now we know that we want to run this test and record its
// success or failure.
- ErrorBitfield testErrors = ERROR_NONE;
+ ErrorBitfield renderErrors = ERROR_NONE;
GrRenderTarget* renderTarget = NULL;
#if SK_SUPPORT_GPU
SkAutoTUnref<GrRenderTarget> rt;
AutoResetGr autogr;
- if ((ERROR_NONE == testErrors) &&
+ if ((ERROR_NONE == renderErrors) &&
kGPU_Backend == config.fBackend) {
GrContext* gr = grFactory->get(config.fGLContextType);
bool grSuccess = false;
@@ -1081,80 +1288,168 @@ int tool_main(int argc, char** argv) {
}
}
if (!grSuccess) {
- testErrors |= ERROR_NO_GPU_CONTEXT;
+ renderErrors |= ERROR_NO_GPU_CONTEXT;
}
}
#endif
- if (ERROR_NONE == testErrors) {
- testErrors |= test_drawing(gm, config,
- writePath, readPath, diffPath,
- GetGr(),
- renderTarget, &forwardRenderedBitmap);
+ SkBitmap comparisonBitmap;
+
+ if (ERROR_NONE == renderErrors) {
+ renderErrors |= gmmain.test_drawing(gm, config, writePath,
+ readPath, diffPath, GetGr(),
+ renderTarget,
+ &comparisonBitmap);
}
- if (doDeferred && !testErrors &&
+ if (doDeferred && !renderErrors &&
(kGPU_Backend == config.fBackend ||
kRaster_Backend == config.fBackend)) {
- testErrors |= test_deferred_drawing(gm, config,
- forwardRenderedBitmap,
- diffPath, GetGr(), renderTarget);
+ renderErrors |= gmmain.test_deferred_drawing(gm, config,
+ comparisonBitmap,
+ diffPath, GetGr(),
+ renderTarget);
}
- if ((ERROR_NONE == testErrors) && doReplay &&
- !(gmFlags & GM::kSkipPicture_Flag)) {
- testErrors |= test_picture_playback(gm, config,
- forwardRenderedBitmap,
- readPath, diffPath);
+ testErrors |= renderErrors;
+ }
+
+ SkBitmap comparisonBitmap;
+ const ConfigData compareConfig =
+ { SkBitmap::kARGB_8888_Config, kRaster_Backend, kDontCare_GLContextType, 0, kRW_ConfigFlag, "comparison" };
+ testErrors |= gmmain.generate_image(gm, compareConfig, NULL, NULL, &comparisonBitmap, false);
+
+ // run the picture centric GM steps
+ if (!(gmFlags & GM::kSkipPicture_Flag)) {
+
+ ErrorBitfield pictErrors = ERROR_NONE;
+
+ //SkAutoTUnref<SkPicture> pict(generate_new_picture(gm));
+ SkPicture* pict = gmmain.generate_new_picture(gm, kNone_BbhType);
+ SkAutoUnref aur(pict);
+
+ if ((ERROR_NONE == testErrors) && doReplay) {
+ SkBitmap bitmap;
+ gmmain.generate_image_from_picture(gm, compareConfig, pict,
+ &bitmap);
+ pictErrors |= gmmain.handle_test_results(gm, compareConfig,
+ NULL, NULL, diffPath,
+ "-replay", bitmap,
+ NULL,
+ &comparisonBitmap);
}
- if ((ERROR_NONE == testErrors) && doPipe &&
- !(gmFlags & GM::kSkipPipe_Flag)) {
- testErrors |= test_pipe_playback(gm, config,
- forwardRenderedBitmap,
- readPath, diffPath);
+ if ((ERROR_NONE == testErrors) &&
+ (ERROR_NONE == pictErrors) &&
+ doSerialize) {
+ SkPicture* repict = gmmain.stream_to_new_picture(*pict);
+ SkAutoUnref aurr(repict);
+
+ SkBitmap bitmap;
+ gmmain.generate_image_from_picture(gm, compareConfig, repict,
+ &bitmap);
+ pictErrors |= gmmain.handle_test_results(gm, compareConfig,
+ NULL, NULL, diffPath,
+ "-serialize", bitmap,
+ NULL,
+ &comparisonBitmap);
}
- if ((ERROR_NONE == testErrors) && doTiledPipe &&
- !SkToBool(gmFlags & (GM::kSkipPipe_Flag | GM::kSkipTiled_Flag))) {
- testErrors |= test_tiled_pipe_playback(gm, config,
- forwardRenderedBitmap,
- readPath, diffPath);
+ if (writePicturePath) {
+ const char* pictureSuffix = "skp";
+ SkString path = gmmain.make_filename(writePicturePath, "",
+ SkString(gm->shortName()),
+ pictureSuffix);
+ SkFILEWStream stream(path.c_str());
+ pict->serialize(&stream);
}
- if ((ERROR_NONE == testErrors) && doSerialize &&
- !(gmFlags & GM::kSkipPicture_Flag)) {
- testErrors |= test_picture_serialization(gm, config,
- forwardRenderedBitmap,
- readPath, diffPath);
+ testErrors |= pictErrors;
+ }
+
+ if (!(gmFlags & GM::kSkipPicture_Flag) && doRTree) {
+ SkPicture* pict = gmmain.generate_new_picture(gm, kRTree_BbhType);
+ SkAutoUnref aur(pict);
+ SkBitmap bitmap;
+ gmmain.generate_image_from_picture(gm, compareConfig, pict,
+ &bitmap);
+ testErrors |= gmmain.handle_test_results(gm, compareConfig,
+ NULL, NULL, diffPath,
+ "-rtree", bitmap,
+ NULL,
+ &comparisonBitmap);
+ }
+
+ if (!(gmFlags & GM::kSkipPicture_Flag) && doTileGrid) {
+ SkPicture* pict = gmmain.generate_new_picture(gm, kTileGrid_BbhType);
+ SkAutoUnref aur(pict);
+ SkBitmap bitmap;
+ gmmain.generate_image_from_picture(gm, compareConfig, pict,
+ &bitmap);
+ testErrors |= gmmain.handle_test_results(gm, compareConfig,
+ NULL, NULL, diffPath,
+ "-tilegrid", bitmap,
+ NULL,
+ &comparisonBitmap);
+ }
+
+ // run the pipe centric GM steps
+ if (!(gmFlags & GM::kSkipPipe_Flag)) {
+
+ ErrorBitfield pipeErrors = ERROR_NONE;
+
+ if ((ERROR_NONE == testErrors) && doPipe) {
+ pipeErrors |= gmmain.test_pipe_playback(gm, compareConfig,
+ comparisonBitmap,
+ readPath, diffPath);
}
- if (!(gmFlags & GM::kSkipPicture_Flag) && writePicturePath) {
- write_picture_serialization(gm, config, writePicturePath);
+ if ((ERROR_NONE == testErrors) &&
+ (ERROR_NONE == pipeErrors) &&
+ doTiledPipe && !(gmFlags & GM::kSkipTiled_Flag)) {
+ pipeErrors |= gmmain.test_tiled_pipe_playback(gm, compareConfig,
+ comparisonBitmap,
+ readPath,
+ diffPath);
}
- // Update overall results.
- // We only tabulate the particular error types that we currently
- // care about (e.g., missing reference images). Later on, if we
- // want to also tabulate pixel mismatches vs dimension mistmatches
- // (or whatever else), we can do so.
- testsRun++;
- if (ERROR_NONE == testErrors) {
- testsPassed++;
- } else if (ERROR_READING_REFERENCE_IMAGE & testErrors) {
- testsMissingReferenceImages++;
- } else {
- testsFailed++;
+ testErrors |= pipeErrors;
+ }
- failedTests.push_back(make_name(shortName, config.fName));
- }
+ // Update overall results.
+ // We only tabulate the particular error types that we currently
+ // care about (e.g., missing reference images). Later on, if we
+ // want to also tabulate pixel mismatches vs dimension mistmatches
+ // (or whatever else), we can do so.
+ testsRun++;
+ if (ERROR_NONE == testErrors) {
+ testsPassed++;
+ } else if (ERROR_READING_REFERENCE_IMAGE & testErrors) {
+ testsMissingReferenceImages++;
+ } else {
+ testsFailed++;
}
+
SkDELETE(gm);
}
SkDebugf("Ran %d tests: %d passed, %d failed, %d missing reference images\n",
testsRun, testsPassed, testsFailed, testsMissingReferenceImages);
- for (int i = 0; i < failedTests.count(); ++i) {
- SkDebugf("\t\t%s\n", failedTests[i].c_str());
+ gmmain.ListErrors();
+
+ if (NULL != writeJsonSummaryPath) {
+ Json::Value actualResults;
+ actualResults[kJsonKey_ActualResults_Failed] =
+ gmmain.fJsonActualResults_Failed;
+ actualResults[kJsonKey_ActualResults_FailureIgnored] =
+ gmmain.fJsonActualResults_FailureIgnored;
+ actualResults[kJsonKey_ActualResults_Succeeded] =
+ gmmain.fJsonActualResults_Succeeded;
+ Json::Value root;
+ root[kJsonKey_ActualResults] = actualResults;
+ root[kJsonKey_ExpectedResults] = gmmain.fJsonExpectedResults;
+ std::string jsonStdString = root.toStyledString();
+ SkFILEWStream stream(writeJsonSummaryPath);
+ stream.write(jsonStdString.c_str(), jsonStdString.length());
}
#if SK_SUPPORT_GPU
@@ -1179,9 +1474,8 @@ int tool_main(int argc, char** argv) {
return (0 == testsFailed) ? 0 : -1;
}
-#if !defined SK_BUILD_FOR_IOS
+#if !defined(SK_BUILD_FOR_IOS) && !defined(SK_BUILD_FOR_NACL)
int main(int argc, char * const argv[]) {
return tool_main(argc, (char**) argv);
}
#endif
-
diff --git a/gm/hairmodes.cpp b/gm/hairmodes.cpp
index 67f1fa370d..6fd72fcf55 100644
--- a/gm/hairmodes.cpp
+++ b/gm/hairmodes.cpp
@@ -79,20 +79,20 @@ namespace skiagm {
class HairModesGM : public GM {
SkPaint fBGPaint;
- public:
- HairModesGM() {
- fBGPaint.setShader(make_bg_shader())->unref();
- }
protected:
- virtual SkString onShortName() {
+ virtual SkString onShortName() SK_OVERRIDE {
return SkString("hairmodes");
}
virtual SkISize onISize() { return make_isize(640, 480); }
- virtual void onDraw(SkCanvas* canvas) {
+ virtual void onOnceBeforeDraw() SK_OVERRIDE {
+ fBGPaint.setShader(make_bg_shader())->unref();
+ }
+
+ virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE {
const SkRect bounds = SkRect::MakeWH(W, H);
static const SkAlpha gAlphaValue[] = { 0xFF, 0x88, 0x88 };
diff --git a/gm/image.cpp b/gm/image.cpp
index a98fa3f333..e54d3c7b2f 100644
--- a/gm/image.cpp
+++ b/gm/image.cpp
@@ -11,7 +11,13 @@
#include "SkStream.h"
#include "SkData.h"
+#if SK_SUPPORT_GPU
+#include "GrContext.h"
+
+namespace skiagm {
extern GrContext* GetGr();
+};
+#endif
static SkData* fileToData(const char path[]) {
SkFILEStream stream(path);
@@ -109,19 +115,40 @@ public:
protected:
virtual SkString onShortName() {
- return SkString("image");
+ return SkString("image-surface");
}
virtual SkISize onISize() {
- return SkISize::Make(640, 480);
+ return SkISize::Make(800, 500);
}
virtual void onDraw(SkCanvas* canvas) {
drawJpeg(canvas, this->getISize());
- canvas->translate(10, 10);
canvas->scale(2, 2);
+ static const char* kLabel1 = "Original Img";
+ static const char* kLabel2 = "Modified Img";
+ static const char* kLabel3 = "Cur Surface";
+
+ static const char* kLabel4 = "Pre-Alloc Img";
+ static const char* kLabel5 = "New Alloc Img";
+ static const char* kLabel6 = "SkPicture";
+ static const char* kLabel7 = "GPU";
+
+ SkPaint textPaint;
+
+ canvas->drawText(kLabel1, strlen(kLabel1), 10, 60, textPaint);
+ canvas->drawText(kLabel2, strlen(kLabel2), 10, 140, textPaint);
+ canvas->drawText(kLabel3, strlen(kLabel3), 10, 220, textPaint);
+
+ canvas->drawText(kLabel4, strlen(kLabel4), 80, 10, textPaint);
+ canvas->drawText(kLabel5, strlen(kLabel5), 160, 10, textPaint);
+ canvas->drawText(kLabel6, strlen(kLabel6), 250, 10, textPaint);
+ canvas->drawText(kLabel7, strlen(kLabel7), 340, 10, textPaint);
+
+ canvas->translate(80, 20);
+
// since we draw into this directly, we need to start fresh
sk_bzero(fBuffer, fBufferSize);
@@ -131,15 +158,26 @@ protected:
info.fHeight = H;
info.fColorType = SkImage::kPMColor_ColorType;
info.fAlphaType = SkImage::kPremul_AlphaType;
- SkAutoTUnref<SkSurface> surf0(SkSurface::NewRasterDirect(info, NULL, fBuffer, RB));
- SkAutoTUnref<SkSurface> surf1(SkSurface::NewRaster(info, NULL));
+ SkAutoTUnref<SkSurface> surf0(SkSurface::NewRasterDirect(info, fBuffer, RB));
+ SkAutoTUnref<SkSurface> surf1(SkSurface::NewRaster(info));
SkAutoTUnref<SkSurface> surf2(SkSurface::NewPicture(info.fWidth, info.fHeight));
+#if SK_SUPPORT_GPU
+ GrContext* ctx = skiagm::GetGr();
+
+ SkAutoTUnref<SkSurface> surf3(SkSurface::NewRenderTarget(ctx, info, 0));
+#endif
test_surface(canvas, surf0);
canvas->translate(80, 0);
test_surface(canvas, surf1);
canvas->translate(80, 0);
test_surface(canvas, surf2);
+#if SK_SUPPORT_GPU
+ if (NULL != ctx) {
+ canvas->translate(80, 0);
+ test_surface(canvas, surf3);
+ }
+#endif
}
virtual uint32_t onGetFlags() const SK_OVERRIDE {
diff --git a/gm/imagefiltersbase.cpp b/gm/imagefiltersbase.cpp
index bcceb5895f..c7f043e724 100644
--- a/gm/imagefiltersbase.cpp
+++ b/gm/imagefiltersbase.cpp
@@ -116,7 +116,7 @@ static void draw_bitmap(SkCanvas* canvas, const SkRect& r, SkImageFilter* imf) {
SkBitmap bm;
bm.setConfig(SkBitmap::kARGB_8888_Config, bounds.width(), bounds.height());
bm.allocPixels();
- bm.eraseColor(0);
+ bm.eraseColor(SK_ColorTRANSPARENT);
SkCanvas c(bm);
draw_path(&c, r, NULL);
@@ -133,7 +133,7 @@ static void draw_sprite(SkCanvas* canvas, const SkRect& r, SkImageFilter* imf) {
SkBitmap bm;
bm.setConfig(SkBitmap::kARGB_8888_Config, bounds.width(), bounds.height());
bm.allocPixels();
- bm.eraseColor(0);
+ bm.eraseColor(SK_ColorTRANSPARENT);
SkCanvas c(bm);
draw_path(&c, r, NULL);
@@ -176,12 +176,10 @@ protected:
SkColorFilter* cf = SkColorFilter::CreateModeFilter(SK_ColorRED,
SkXfermode::kSrcIn_Mode);
SkImageFilter* filters[] = {
-#if 1
NULL,
new IdentityImageFilter,
new FailImageFilter,
- new SkColorFilterImageFilter(cf),
-#endif
+ SkColorFilterImageFilter::Create(cf),
new SkBlurImageFilter(12.0f, 0.0f),
};
cf->unref();
diff --git a/gm/imagefiltersgraph.cpp b/gm/imagefiltersgraph.cpp
index 816c88e97a..9c70770d46 100644
--- a/gm/imagefiltersgraph.cpp
+++ b/gm/imagefiltersgraph.cpp
@@ -8,9 +8,12 @@
#include "gm.h"
#include "SkBitmapSource.h"
+#include "SkBlendImageFilter.h"
#include "SkBlurImageFilter.h"
#include "SkColorFilter.h"
+#include "SkColorMatrixFilter.h"
#include "SkColorFilterImageFilter.h"
+#include "SkMergeImageFilter.h"
#include "SkMorphologyImageFilter.h"
#include "SkTestImageFilters.h"
@@ -40,7 +43,7 @@ protected:
canvas.drawText(str, strlen(str), SkIntToScalar(20), SkIntToScalar(70), paint);
}
- virtual SkISize onISize() { return SkISize::Make(500, 500); }
+ virtual SkISize onISize() { return SkISize::Make(200, 100); }
virtual void onDraw(SkCanvas* canvas) {
if (!fInitialized) {
@@ -48,19 +51,35 @@ protected:
fInitialized = true;
}
canvas->clear(0x00000000);
+ {
+ SkAutoTUnref<SkImageFilter> bitmapSource(new SkBitmapSource(fBitmap));
+ SkAutoTUnref<SkColorFilter> cf(SkColorFilter::CreateModeFilter(SK_ColorRED,
+ SkXfermode::kSrcIn_Mode));
+ SkAutoTUnref<SkImageFilter> blur(new SkBlurImageFilter(4.0f, 4.0f, bitmapSource));
+ SkAutoTUnref<SkImageFilter> erode(new SkErodeImageFilter(4, 4, blur));
+ SkAutoTUnref<SkImageFilter> color(SkColorFilterImageFilter::Create(cf, erode));
+ SkAutoTUnref<SkImageFilter> merge(new SkMergeImageFilter(blur, color));
+
+ SkPaint paint;
+ paint.setImageFilter(merge);
+ canvas->drawPaint(paint);
+ }
+ {
+ SkAutoTUnref<SkImageFilter> morph(new SkDilateImageFilter(5, 5));
- SkAutoTUnref<SkImageFilter> bitmapSource(new SkBitmapSource(fBitmap));
+ SkScalar matrix[20] = { SK_Scalar1, 0, 0, 0, 0,
+ 0, SK_Scalar1, 0, 0, 0,
+ 0, 0, SK_Scalar1, 0, 0,
+ 0, 0, 0, SkFloatToScalar(0.5f), 0 };
- SkAutoTUnref<SkColorFilter> cf(SkColorFilter::CreateModeFilter(SK_ColorRED,
- SkXfermode::kSrcIn_Mode));
- SkAutoTUnref<SkImageFilter> blur(new SkBlurImageFilter(4.0f, 4.0f, bitmapSource));
- SkAutoTUnref<SkImageFilter> erode(new SkErodeImageFilter(4, 4, blur));
- SkAutoTUnref<SkImageFilter> color(new SkColorFilterImageFilter(cf, erode));
- SkAutoTUnref<SkImageFilter> merge(new SkMergeImageFilter(blur, color));
+ SkAutoTUnref<SkColorFilter> matrixFilter(new SkColorMatrixFilter(matrix));
+ SkAutoTUnref<SkImageFilter> colorMorph(SkColorFilterImageFilter::Create(matrixFilter, morph));
+ SkAutoTUnref<SkImageFilter> blendColor(new SkBlendImageFilter(SkBlendImageFilter::kNormal_Mode, colorMorph));
- SkPaint paint;
- paint.setImageFilter(merge);
- canvas->drawPaint(paint);
+ SkPaint paint;
+ paint.setImageFilter(blendColor);
+ canvas->drawBitmap(fBitmap, 100, 0, &paint);
+ }
}
private:
diff --git a/gm/lighting.cpp b/gm/lighting.cpp
index 46474abc35..9db34fde3d 100644
--- a/gm/lighting.cpp
+++ b/gm/lighting.cpp
@@ -47,6 +47,18 @@ protected:
make_bitmap();
fInitialized = true;
}
+ canvas->clear(0xFF101010);
+ SkPaint checkPaint;
+ checkPaint.setColor(0xFF202020);
+ for (int y = 0; y < HEIGHT; y += 16) {
+ for (int x = 0; x < WIDTH; x += 16) {
+ canvas->save();
+ canvas->translate(SkIntToScalar(x), SkIntToScalar(y));
+ canvas->drawRect(SkRect::MakeXYWH(8, 0, 8, 8), checkPaint);
+ canvas->drawRect(SkRect::MakeXYWH(0, 8, 8, 8), checkPaint);
+ canvas->restore();
+ }
+ }
SkPoint3 pointLocation(0, 0, SkIntToScalar(10));
SkScalar azimuthRad = SkDegreesToRadians(SkIntToScalar(225));
SkScalar elevationRad = SkDegreesToRadians(SkIntToScalar(5));
diff --git a/gm/modecolorfilters.cpp b/gm/modecolorfilters.cpp
new file mode 100644
index 0000000000..fa0c09118c
--- /dev/null
+++ b/gm/modecolorfilters.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+#include "SkBitmapProcShader.h"
+#include "SkColorFilter.h"
+#include "SkGradientShader.h"
+
+#define WIDTH 512
+#define HEIGHT 1024
+
+namespace skiagm {
+
+// Using gradients because GPU doesn't currently have an implementation of SkColorShader (duh!)
+static SkShader* make_color_shader(SkColor color) {
+ static const SkPoint kPts[] = {{0, 0}, {1, 1}};
+ SkColor colors[] = {color, color};
+
+ return SkGradientShader::CreateLinear(kPts, colors, NULL, 2, SkShader::kClamp_TileMode);
+}
+
+static SkShader* make_solid_shader() {
+ return make_color_shader(SkColorSetARGB(0xFF, 0x40, 0x80, 0x20));
+}
+
+static SkShader* make_transparent_shader() {
+ return make_color_shader(SkColorSetARGB(0x80, 0x10, 0x70, 0x20));
+}
+
+static SkShader* make_trans_black_shader() {
+ return make_color_shader(0x0);
+}
+
+// draws a background behind each test rect to see transparency
+static SkShader* make_bg_shader(int checkSize) {
+ SkBitmap bmp;
+ bmp.setConfig(SkBitmap::kARGB_8888_Config, 2 * checkSize, 2 * checkSize);
+ bmp.allocPixels();
+ SkCanvas canvas(bmp);
+ canvas.clear(0xFF800000);
+ SkPaint paint;
+ paint.setColor(0xFF000080);
+ SkRect rect0 = SkRect::MakeXYWH(0, 0,
+ SkIntToScalar(checkSize), SkIntToScalar(checkSize));
+ SkRect rect1 = SkRect::MakeXYWH(SkIntToScalar(checkSize), SkIntToScalar(checkSize),
+ SkIntToScalar(checkSize), SkIntToScalar(checkSize));
+ canvas.drawRect(rect1, paint);
+ canvas.drawRect(rect0, paint);
+ return SkNEW_ARGS(SkBitmapProcShader, (bmp, SkShader::kRepeat_TileMode,
+ SkShader::kRepeat_TileMode));
+}
+
+class ModeColorFilterGM : public GM {
+public:
+ ModeColorFilterGM() {
+ this->setBGColor(0xFF303030);
+ }
+
+protected:
+ virtual SkString onShortName() {
+ return SkString("modecolorfilters");
+ }
+
+ virtual SkISize onISize() {
+ return make_isize(WIDTH, HEIGHT);
+ }
+
+ virtual void onDraw(SkCanvas* canvas) {
+ // size of rect for each test case
+ static const int kRectWidth = 20;
+ static const int kRectHeight = 20;
+
+ static const int kCheckSize = 10;
+
+ if (!fBmpShader) {
+ fBmpShader.reset(make_bg_shader(kCheckSize));
+ }
+ SkPaint bgPaint;
+ bgPaint.setShader(fBmpShader);
+ bgPaint.setXfermodeMode(SkXfermode::kSrc_Mode);
+
+ SkShader* shaders[] = {
+ NULL, // use a paint color instead of a shader
+ make_solid_shader(),
+ make_transparent_shader(),
+ make_trans_black_shader(),
+ };
+
+ // used without shader
+ SkColor colors[] = {
+ SkColorSetARGB(0xFF, 0xFF, 0xFF, 0xFF),
+ SkColorSetARGB(0xFF, 0x00, 0x00, 0x00),
+ SkColorSetARGB(0x00, 0x00, 0x00, 0x00),
+ SkColorSetARGB(0xFF, 0x10, 0x20, 0x40),
+ SkColorSetARGB(0xA0, 0x20, 0x30, 0x90),
+ };
+
+ // used with shaders
+ SkColor alphas[] = {0xFFFFFFFF, 0x80808080};
+
+ SkXfermode::Mode modes[] = { // currently just doing the Modes expressible as Coeffs
+ SkXfermode::kClear_Mode,
+ SkXfermode::kSrc_Mode,
+ SkXfermode::kDst_Mode,
+ SkXfermode::kSrcOver_Mode,
+ SkXfermode::kDstOver_Mode,
+ SkXfermode::kSrcIn_Mode,
+ SkXfermode::kDstIn_Mode,
+ SkXfermode::kSrcOut_Mode,
+ SkXfermode::kDstOut_Mode,
+ SkXfermode::kSrcATop_Mode,
+ SkXfermode::kDstATop_Mode,
+ SkXfermode::kXor_Mode,
+ SkXfermode::kPlus_Mode,
+ SkXfermode::kMultiply_Mode,
+ };
+
+ SkPaint paint;
+ int idx = 0;
+ static const int kRectsPerRow = SkMax32(this->getISize().fWidth / kRectWidth, 1);
+ for (size_t cfm = 0; cfm < SK_ARRAY_COUNT(modes); ++cfm) {
+ for (size_t cfc = 0; cfc < SK_ARRAY_COUNT(colors); ++cfc) {
+ SkAutoTUnref<SkColorFilter> cf(SkColorFilter::CreateModeFilter(colors[cfc],
+ modes[cfm]));
+ paint.setColorFilter(cf);
+ for (size_t s = 0; s < SK_ARRAY_COUNT(shaders); ++s) {
+ paint.setShader(shaders[s]);
+ bool hasShader = NULL == paint.getShader();
+ int paintColorCnt = hasShader ? SK_ARRAY_COUNT(alphas) : SK_ARRAY_COUNT(colors);
+ SkColor* paintColors = hasShader ? alphas : colors;
+ for (int pc = 0; pc < paintColorCnt; ++pc) {
+ paint.setColor(paintColors[pc]);
+ SkScalar x = SkIntToScalar(idx % kRectsPerRow);
+ SkScalar y = SkIntToScalar(idx / kRectsPerRow);
+ SkRect rect = SkRect::MakeXYWH(x * kRectWidth, y * kRectHeight,
+ SkIntToScalar(kRectWidth),
+ SkIntToScalar(kRectHeight));
+ canvas->drawRect(rect, bgPaint);
+ canvas->drawRect(rect, paint);
+ ++idx;
+ }
+ }
+ }
+ }
+
+ for (size_t i = 0; i < SK_ARRAY_COUNT(shaders); ++i) {
+ SkSafeUnref(shaders[i]);
+ }
+ }
+
+private:
+ SkAutoTUnref<SkShader> fBmpShader;
+ typedef GM INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static GM* MyFactory(void*) { return new ModeColorFilterGM; }
+static GMRegistry reg(MyFactory);
+
+}
diff --git a/gm/ninepatchstretch.cpp b/gm/ninepatchstretch.cpp
index 38b6f7d012..773a692e65 100644
--- a/gm/ninepatchstretch.cpp
+++ b/gm/ninepatchstretch.cpp
@@ -34,7 +34,7 @@ static void make_bitmap(SkBitmap* bitmap, GrContext* ctx, SkIRect* center) {
SkCanvas canvas(dev);
dev->unref();
- canvas.clear(0);
+ canvas.clear(SK_ColorTRANSPARENT);
SkRect r = SkRect::MakeWH(SkIntToScalar(kSize), SkIntToScalar(kSize));
const SkScalar strokeWidth = SkIntToScalar(6);
diff --git a/gm/pathinterior.cpp b/gm/pathinterior.cpp
new file mode 100644
index 0000000000..f7e0b5e867
--- /dev/null
+++ b/gm/pathinterior.cpp
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+#include "SkCanvas.h"
+#include "SkGraphics.h"
+#include "SkRandom.h"
+#include "SkLayerDrawLooper.h"
+#include "SkBlurMaskFilter.h"
+
+static SkRect inset(const SkRect& r) {
+ SkRect rect = r;
+ rect.inset(r.width() / 8, r.height() / 8);
+ return rect;
+}
+
+class PathInteriorGM : public skiagm::GM {
+public:
+ PathInteriorGM() {
+ this->setBGColor(0xFFDDDDDD);
+ }
+
+protected:
+ virtual SkISize onISize() {
+ return SkISize::Make(770, 770);
+ }
+
+ virtual SkString onShortName() SK_OVERRIDE {
+ return SkString("pathinterior");
+ }
+
+ void show(SkCanvas* canvas, const SkPath& path) {
+ SkPaint paint;
+ paint.setAntiAlias(true);
+
+ SkRect rect;
+#if 0
+ bool hasInterior = path.hasRectangularInterior(&rect);
+#else
+ bool hasInterior = false;
+#endif
+
+ paint.setColor(hasInterior ? 0xFF8888FF : SK_ColorGRAY);
+ canvas->drawPath(path, paint);
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setColor(SK_ColorRED);
+ canvas->drawPath(path, paint);
+
+ if (hasInterior) {
+ paint.setStyle(SkPaint::kFill_Style);
+ paint.setColor(0x8800FF00);
+ canvas->drawRect(rect, paint);
+ }
+ }
+
+ virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE {
+ canvas->translate(8.5f, 8.5f);
+
+ const SkRect rect = { 0, 0, 80, 80 };
+ const SkScalar RAD = rect.width()/8;
+
+ int i = 0;
+ for (int insetFirst = 0; insetFirst <= 1; ++insetFirst) {
+ for (int doEvenOdd = 0; doEvenOdd <= 1; ++doEvenOdd) {
+ for (int outerRR = 0; outerRR <= 1; ++outerRR) {
+ for (int innerRR = 0; innerRR <= 1; ++innerRR) {
+ for (int outerCW = 0; outerCW <= 1; ++outerCW) {
+ for (int innerCW = 0; innerCW <= 1; ++innerCW) {
+ SkPath path;
+ path.setFillType(doEvenOdd ? SkPath::kEvenOdd_FillType : SkPath::kWinding_FillType);
+ SkPath::Direction outerDir = outerCW ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
+ SkPath::Direction innerDir = innerCW ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
+
+ SkRect r = insetFirst ? inset(rect) : rect;
+ if (outerRR) {
+ path.addRoundRect(r, RAD, RAD, outerDir);
+ } else {
+ path.addRect(r, outerDir);
+ }
+ r = insetFirst ? rect : inset(rect);
+ if (innerRR) {
+ path.addRoundRect(r, RAD, RAD, innerDir);
+ } else {
+ path.addRect(r, innerDir);
+ }
+
+ SkScalar dx = (i / 8) * rect.width() * 6 / 5;
+ SkScalar dy = (i % 8) * rect.height() * 6 / 5;
+ i++;
+ path.offset(dx, dy);
+
+ this->show(canvas, path);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+private:
+
+ typedef GM INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static skiagm::GM* MyFactory(void*) { return new PathInteriorGM; }
+static skiagm::GMRegistry reg(MyFactory);
+
diff --git a/gm/resources/plane.png b/gm/resources/plane.png
new file mode 100644
index 0000000000..03585b5cb4
--- /dev/null
+++ b/gm/resources/plane.png
Binary files differ
diff --git a/gm/rrect.cpp b/gm/rrect.cpp
new file mode 100644
index 0000000000..b07e3c0350
--- /dev/null
+++ b/gm/rrect.cpp
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+#include "SkCanvas.h"
+#include "SkRRect.h"
+#include "SkPath.h"
+
+typedef void (*InsetProc)(const SkRRect&, SkScalar dx, SkScalar dy, SkRRect*);
+
+static void inset0(const SkRRect& src, SkScalar dx, SkScalar dy, SkRRect* dst) {
+ SkRect r = src.rect();
+
+ r.inset(dx, dy);
+ if (r.isEmpty()) {
+ dst->setEmpty();
+ return;
+ }
+
+ SkVector radii[4];
+ for (int i = 0; i < 4; ++i) {
+ radii[i] = src.radii((SkRRect::Corner)i);
+ }
+ for (int i = 0; i < 4; ++i) {
+ radii[i].fX -= dx;
+ radii[i].fY -= dy;
+ }
+ dst->setRectRadii(r, radii);
+}
+
+static void inset1(const SkRRect& src, SkScalar dx, SkScalar dy, SkRRect* dst) {
+ SkRect r = src.rect();
+
+ r.inset(dx, dy);
+ if (r.isEmpty()) {
+ dst->setEmpty();
+ return;
+ }
+
+ SkVector radii[4];
+ for (int i = 0; i < 4; ++i) {
+ radii[i] = src.radii((SkRRect::Corner)i);
+ }
+ dst->setRectRadii(r, radii);
+}
+
+static void inset2(const SkRRect& src, SkScalar dx, SkScalar dy, SkRRect* dst) {
+ SkRect r = src.rect();
+
+ r.inset(dx, dy);
+ if (r.isEmpty()) {
+ dst->setEmpty();
+ return;
+ }
+
+ SkVector radii[4];
+ for (int i = 0; i < 4; ++i) {
+ radii[i] = src.radii((SkRRect::Corner)i);
+ }
+ for (int i = 0; i < 4; ++i) {
+ if (radii[i].fX) {
+ radii[i].fX -= dx;
+ }
+ if (radii[i].fY) {
+ radii[i].fY -= dy;
+ }
+ }
+ dst->setRectRadii(r, radii);
+}
+
+static SkScalar prop(SkScalar radius, SkScalar delta, SkScalar newSize, SkScalar oldSize) {
+ return newSize * radius / oldSize;
+}
+
+static void inset3(const SkRRect& src, SkScalar dx, SkScalar dy, SkRRect* dst) {
+ SkRect r = src.rect();
+
+ r.inset(dx, dy);
+ if (r.isEmpty()) {
+ dst->setEmpty();
+ return;
+ }
+
+ SkVector radii[4];
+ for (int i = 0; i < 4; ++i) {
+ radii[i] = src.radii((SkRRect::Corner)i);
+ }
+ for (int i = 0; i < 4; ++i) {
+ radii[i].fX = prop(radii[i].fX, dx, r.width(), src.rect().width());
+ radii[i].fY = prop(radii[i].fY, dy, r.height(), src.rect().height());
+ }
+ dst->setRectRadii(r, radii);
+}
+
+static void draw_rrect_color(SkCanvas* canvas, const SkRRect& rrect) {
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setStyle(SkPaint::kStroke_Style);
+
+ if (rrect.isRect()) {
+ paint.setColor(SK_ColorRED);
+ } else if (rrect.isOval()) {
+ paint.setColor(0xFF008800);
+ } else if (rrect.isSimple()) {
+ paint.setColor(SK_ColorBLUE);
+ } else {
+ paint.setColor(SK_ColorBLACK);
+ }
+ canvas->drawRRect(rrect, paint);
+}
+
+static void drawrr(SkCanvas* canvas, const SkRRect& rrect, InsetProc proc) {
+ SkRRect rr;
+ for (SkScalar d = -30; d <= 30; d += 5) {
+ proc(rrect, d, d, &rr);
+ draw_rrect_color(canvas, rr);
+ }
+}
+
+class RRectGM : public skiagm::GM {
+public:
+ RRectGM() {}
+
+protected:
+ virtual SkString onShortName() {
+ return SkString("rrect");
+ }
+
+ virtual SkISize onISize() {
+ return SkISize::Make(820, 710);
+ }
+
+ virtual void onDraw(SkCanvas* canvas) {
+ static const InsetProc insetProcs[] = {
+ inset0, inset1, inset2, inset3
+ };
+
+ SkRRect rrect[4];
+ SkRect r = { 0, 0, 120, 100 };
+ SkVector radii[4] = {
+ { 0, 0 }, { 30, 1 }, { 10, 40 }, { 40, 40 }
+ };
+
+ rrect[0].setRect(r);
+ rrect[1].setOval(r);
+ rrect[2].setRectXY(r, 20, 20);
+ rrect[3].setRectRadii(r, radii);
+
+ canvas->translate(50.5f, 50.5f);
+ for (size_t j = 0; j < SK_ARRAY_COUNT(insetProcs); ++j) {
+ canvas->save();
+ for (size_t i = 0; i < SK_ARRAY_COUNT(rrect); ++i) {
+ drawrr(canvas, rrect[i], insetProcs[j]);
+ canvas->translate(200, 0);
+ }
+ canvas->restore();
+ canvas->translate(0, 170);
+ }
+ }
+
+private:
+ typedef GM INHERITED;
+};
+
+DEF_GM( return new RRectGM; )
+
diff --git a/gm/rrects.cpp b/gm/rrects.cpp
new file mode 100644
index 0000000000..cccece9d30
--- /dev/null
+++ b/gm/rrects.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+#include "SkRRect.h"
+
+namespace skiagm {
+
+///////////////////////////////////////////////////////////////////////////////
+
+class RRectGM : public GM {
+public:
+ RRectGM(bool doAA, bool doClip) : fDoAA(doAA), fDoClip(doClip) {
+ this->setBGColor(0xFFDDDDDD);
+ this->setUpRRects();
+ }
+
+protected:
+ SkString onShortName() {
+ SkString name("rrect");
+ if (fDoClip) {
+ name.append("_clip");
+ }
+ if (fDoAA) {
+ name.append("_aa");
+ } else {
+ name.append("_bw");
+ }
+
+ return name;
+ }
+
+ virtual SkISize onISize() { return make_isize(kImageWidth, kImageHeight); }
+
+ virtual void onDraw(SkCanvas* canvas) {
+
+ SkPaint paint;
+ // when clipping the AA is pushed into the clip operation
+ paint.setAntiAlias(fDoClip ? false : fDoAA);
+
+ static const SkRect kMaxTileBound = SkRect::MakeWH(SkIntToScalar(kTileX), SkIntToScalar(kTileY));
+
+ int curRRect = 0;
+ for (int y = 1; y < kImageHeight; y += kTileY) {
+ for (int x = 1; x < kImageWidth; x += kTileX) {
+ if (curRRect >= kNumRRects) {
+ break;
+ }
+ SkASSERT(kMaxTileBound.contains(fRRects[curRRect].getBounds()));
+
+ canvas->save();
+ canvas->translate(SkIntToScalar(x), SkIntToScalar(y));
+ if (fDoClip) {
+ canvas->clipRRect(fRRects[curRRect], SkRegion::kReplace_Op, fDoAA);
+ canvas->drawRect(kMaxTileBound, paint);
+ } else {
+ canvas->drawRRect(fRRects[curRRect], paint);
+ }
+ ++curRRect;
+ canvas->restore();
+ }
+ }
+ }
+
+ void setUpRRects() {
+ // each RRect must fit in a 0x0 -> (kTileX-2)x(kTileY-2) block. These will be tiled across
+ // the screen in kTileX x kTileY tiles. The extra empty pixels on each side are for AA.
+
+ // simple cases
+ fRRects[0].setRect(SkRect::MakeWH(kTileX-2, kTileY-2));
+ fRRects[1].setOval(SkRect::MakeWH(kTileX-2, kTileY-2));
+ fRRects[2].setRectXY(SkRect::MakeWH(kTileX-2, kTileY-2), 10, 10);
+
+ // The first complex case needs special handling since it is a square
+ fRRects[kNumSimpleCases].setRectRadii(SkRect::MakeWH(kTileY-2, kTileY-2), gRadii[0]);
+ for (int i = 1; i < SK_ARRAY_COUNT(gRadii); ++i) {
+ fRRects[kNumSimpleCases+i].setRectRadii(SkRect::MakeWH(kTileX-2, kTileY-2), gRadii[i]);
+ }
+ }
+
+private:
+ bool fDoAA;
+ bool fDoClip; // use clipRRect & drawRect instead of drawRRect
+
+ static const int kImageWidth = 640;
+ static const int kImageHeight = 480;
+
+ static const int kTileX = 80;
+ static const int kTileY = 40;
+
+ static const int kNumSimpleCases = 3;
+ static const int kNumComplexCases = 19;
+ static const SkVector gRadii[kNumComplexCases][4];
+
+ static const int kNumRRects = kNumSimpleCases + kNumComplexCases;
+ SkRRect fRRects[kNumRRects];
+
+ typedef GM INHERITED;
+};
+
+// Radii for the various test cases. Order is UL, UR, LR, LL
+const SkVector RRectGM::gRadii[kNumComplexCases][4] = {
+ // a circle
+ { { kTileY, kTileY }, { kTileY, kTileY }, { kTileY, kTileY }, { kTileY, kTileY } },
+
+ // odd ball cases
+ { { 8, 8 }, { 32, 32 }, { 8, 8 }, { 32, 32 } },
+ { { 16, 8 }, { 8, 16 }, { 16, 8 }, { 8, 16 } },
+ { { 0, 0 }, { 16, 16 }, { 8, 8 }, { 32, 32 } },
+
+ // UL
+ { { 30, 30 }, { 0, 0 }, { 0, 0 }, { 0, 0 } },
+ { { 30, 15 }, { 0, 0 }, { 0, 0 }, { 0, 0 } },
+ { { 15, 30 }, { 0, 0 }, { 0, 0 }, { 0, 0 } },
+
+ // UR
+ { { 0, 0 }, { 30, 30 }, { 0, 0 }, { 0, 0 } },
+ { { 0, 0 }, { 30, 15 }, { 0, 0 }, { 0, 0 } },
+ { { 0, 0 }, { 15, 30 }, { 0, 0 }, { 0, 0 } },
+
+ // LR
+ { { 0, 0 }, { 0, 0 }, { 30, 30 }, { 0, 0 } },
+ { { 0, 0 }, { 0, 0 }, { 30, 15 }, { 0, 0 } },
+ { { 0, 0 }, { 0, 0 }, { 15, 30 }, { 0, 0 } },
+
+ // LL
+ { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 30, 30 } },
+ { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 30, 15 } },
+ { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 15, 30 } },
+
+ // over-sized radii
+ { { 0, 0 }, { 100, 400 }, { 0, 0 }, { 0, 0 } },
+ { { 0, 0 }, { 400, 400 }, { 0, 0 }, { 0, 0 } },
+ { { 400, 400 }, { 400, 400 }, { 400, 400 }, { 400, 400 } },
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+DEF_GM( return new RRectGM(false, false); )
+DEF_GM( return new RRectGM(true, false); )
+DEF_GM( return new RRectGM(false, true); )
+DEF_GM( return new RRectGM(true, true); )
+
+}
+
+
diff --git a/gm/shadertext.cpp b/gm/shadertext.cpp
index d8641db1c2..0e092dc6d9 100644
--- a/gm/shadertext.cpp
+++ b/gm/shadertext.cpp
@@ -15,7 +15,7 @@ namespace skiagm {
static void makebm(SkBitmap* bm, SkBitmap::Config config, int w, int h) {
bm->setConfig(config, w, h);
bm->allocPixels();
- bm->eraseColor(0);
+ bm->eraseColor(SK_ColorTRANSPARENT);
SkCanvas canvas(*bm);
SkScalar s = SkIntToScalar(SkMin32(w, h));
diff --git a/gm/shadertext2.cpp b/gm/shadertext2.cpp
index 387bd498b0..e760e548d5 100644
--- a/gm/shadertext2.cpp
+++ b/gm/shadertext2.cpp
@@ -14,7 +14,7 @@ namespace skiagm {
static void makebm(SkBitmap* bm, SkBitmap::Config config, int w, int h) {
bm->setConfig(config, w, h);
bm->allocPixels();
- bm->eraseColor(0);
+ bm->eraseColor(SK_ColorTRANSPARENT);
SkCanvas canvas(*bm);
SkScalar s = SkIntToScalar(SkMin32(w, h));
diff --git a/gm/shadertext3.cpp b/gm/shadertext3.cpp
index 4e9558801a..fa327c8bff 100644
--- a/gm/shadertext3.cpp
+++ b/gm/shadertext3.cpp
@@ -14,7 +14,7 @@ namespace skiagm {
static void makebm(SkBitmap* bm, SkBitmap::Config config, int w, int h) {
bm->setConfig(config, w, h);
bm->allocPixels();
- bm->eraseColor(0);
+ bm->eraseColor(SK_ColorTRANSPARENT);
SkCanvas canvas(*bm);
SkScalar s = SkIntToScalar(SkMin32(w, h));
@@ -118,7 +118,7 @@ protected:
canvas->drawText(kText, kTextLen, 0, 0, fillPaint);
canvas->drawText(kText, kTextLen, 0, 0, outlinePaint);
- int w = fillPaint.measureText(kText, kTextLen);
+ SkScalar w = fillPaint.measureText(kText, kTextLen);
canvas->translate(w + 10.f, 0.f);
++i;
if (!(i % 2)) {
diff --git a/gm/simpleaaclip.cpp b/gm/simpleaaclip.cpp
index c6e8ba9b5c..885226b6ef 100644
--- a/gm/simpleaaclip.cpp
+++ b/gm/simpleaaclip.cpp
@@ -195,18 +195,8 @@ private:
//////////////////////////////////////////////////////////////////////////////
// rects
-static GM* MyFactory(void*) { return new SimpleClipGM(
- SimpleClipGM::kRect_GeomType); }
-static GMRegistry reg(MyFactory);
-
-// paths
-static GM* MyFactory2(void*) { return new SimpleClipGM(
- SimpleClipGM::kPath_GeomType); }
-static GMRegistry reg2(MyFactory2);
-
-// aa clip
-static GM* MyFactory3(void*) { return new SimpleClipGM(
- SimpleClipGM::kAAClip_GeomType); }
-static GMRegistry reg3(MyFactory3);
+DEF_GM( return new SimpleClipGM(SimpleClipGM::kRect_GeomType); )
+DEF_GM( return new SimpleClipGM(SimpleClipGM::kPath_GeomType); )
+DEF_GM( return new SimpleClipGM(SimpleClipGM::kAAClip_GeomType); )
}
diff --git a/gm/srcmode.cpp b/gm/srcmode.cpp
new file mode 100644
index 0000000000..c99a6b2f9d
--- /dev/null
+++ b/gm/srcmode.cpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+#include "SkCanvas.h"
+#include "SkGradientShader.h"
+#include "SkSurface.h"
+
+#if SK_SUPPORT_GPU
+ #include "SkGpuDevice.h"
+#endif
+
+#define W SkIntToScalar(80)
+#define H SkIntToScalar(60)
+
+typedef void (*PaintProc)(SkPaint*);
+
+static void identity_paintproc(SkPaint* paint) {
+ paint->setShader(NULL);
+}
+
+static void gradient_paintproc(SkPaint* paint) {
+ const SkColor colors[] = { SK_ColorGREEN, SK_ColorBLUE };
+ const SkPoint pts[] = { { 0, 0 }, { W, H } };
+ SkShader* s = SkGradientShader::CreateLinear(pts, colors, NULL,
+ SK_ARRAY_COUNT(colors),
+ SkShader::kClamp_TileMode);
+ paint->setShader(s)->unref();
+}
+
+typedef void (*Proc)(SkCanvas*, const SkPaint&);
+
+static void draw_hair(SkCanvas* canvas, const SkPaint& paint) {
+ SkPaint p(paint);
+ p.setStrokeWidth(0);
+ canvas->drawLine(0, 0, W, H, p);
+}
+
+static void draw_thick(SkCanvas* canvas, const SkPaint& paint) {
+ SkPaint p(paint);
+ p.setStrokeWidth(H/5);
+ canvas->drawLine(0, 0, W, H, p);
+}
+
+static void draw_rect(SkCanvas* canvas, const SkPaint& paint) {
+ canvas->drawRect(SkRect::MakeWH(W, H), paint);
+}
+
+static void draw_oval(SkCanvas* canvas, const SkPaint& paint) {
+ canvas->drawOval(SkRect::MakeWH(W, H), paint);
+}
+
+static void draw_text(SkCanvas* canvas, const SkPaint& paint) {
+ SkPaint p(paint);
+ p.setTextSize(H/4);
+ canvas->drawText("Hamburge", 8, 0, H*2/3, p);
+}
+
+class SrcModeGM : public skiagm::GM {
+ SkPath fPath;
+public:
+ SrcModeGM() {
+ this->setBGColor(SK_ColorBLACK);
+ }
+
+protected:
+ virtual SkString onShortName() {
+ return SkString("srcmode");
+ }
+
+ virtual SkISize onISize() {
+ return SkISize::Make(640, 760);
+ }
+
+ void drawContent(SkCanvas* canvas) {
+ canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
+
+ SkPaint paint;
+ paint.setColor(0x80FF0000);
+
+ const Proc procs[] = {
+ draw_hair, draw_thick, draw_rect, draw_oval, draw_text
+ };
+
+ const SkXfermode::Mode modes[] = {
+ SkXfermode::kSrcOver_Mode, SkXfermode::kSrc_Mode, SkXfermode::kClear_Mode
+ };
+
+ const PaintProc paintProcs[] = {
+ identity_paintproc, gradient_paintproc
+ };
+
+ for (int aa = 0; aa <= 1; ++aa) {
+ paint.setAntiAlias(SkToBool(aa));
+ canvas->save();
+ for (size_t i = 0; i < SK_ARRAY_COUNT(paintProcs); ++i) {
+ paintProcs[i](&paint);
+ for (size_t x = 0; x < SK_ARRAY_COUNT(modes); ++x) {
+ paint.setXfermodeMode(modes[x]);
+ canvas->save();
+ for (size_t y = 0; y < SK_ARRAY_COUNT(procs); ++y) {
+ procs[y](canvas, paint);
+ canvas->translate(0, H * 5 / 4);
+ }
+ canvas->restore();
+ canvas->translate(W * 5 / 4, 0);
+ }
+ }
+ canvas->restore();
+ canvas->translate(0, (H * 5 / 4) * SK_ARRAY_COUNT(procs));
+ }
+ }
+
+ static SkSurface* compat_surface(SkCanvas* canvas, const SkISize& size,
+ bool skipGPU) {
+ SkImage::Info info = {
+ size.width(),
+ size.height(),
+ SkImage::kPMColor_ColorType,
+ SkImage::kPremul_AlphaType
+ };
+#if SK_SUPPORT_GPU
+ SkDevice* dev = canvas->getDevice();
+ if (!skipGPU && dev->accessRenderTarget()) {
+ SkGpuDevice* gd = (SkGpuDevice*)dev;
+ GrContext* ctx = gd->context();
+ return SkSurface::NewRenderTarget(ctx, info, 0);
+ }
+#endif
+ return SkSurface::NewRaster(info);
+ }
+
+ virtual void onDraw(SkCanvas* canvas) {
+ SkAutoTUnref<SkSurface> surf(compat_surface(canvas, this->getISize(),
+ this->isCanvasDeferred()));
+ surf->getCanvas()->drawColor(SK_ColorWHITE);
+ this->drawContent(surf->getCanvas());
+ surf->draw(canvas, 0, 0, NULL);
+ }
+
+private:
+ typedef skiagm::GM INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+DEF_GM(return new SrcModeGM;)
+
diff --git a/gm/strokerect.cpp b/gm/strokerect.cpp
new file mode 100644
index 0000000000..99a50ef4f4
--- /dev/null
+++ b/gm/strokerect.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+#include "SkCanvas.h"
+#include "SkPath.h"
+
+#define STROKE_WIDTH SkIntToScalar(20)
+
+static void draw_path(SkCanvas* canvas, const SkPath& path, const SkRect& rect,
+ SkPaint::Join join, int doFill) {
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setStyle(doFill ? SkPaint::kStrokeAndFill_Style : SkPaint::kStroke_Style);
+
+ paint.setColor(SK_ColorGRAY);
+ paint.setStrokeWidth(STROKE_WIDTH);
+ paint.setStrokeJoin(join);
+ canvas->drawRect(rect, paint);
+
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setStrokeWidth(0);
+ paint.setColor(SK_ColorRED);
+ canvas->drawPath(path, paint);
+
+ paint.setStrokeWidth(3);
+ paint.setStrokeJoin(SkPaint::kMiter_Join);
+ int n = path.countPoints();
+ SkAutoTArray<SkPoint> points(n);
+ path.getPoints(points.get(), n);
+ canvas->drawPoints(SkCanvas::kPoints_PointMode, n, points.get(), paint);
+}
+
+/*
+ * Test calling SkStroker for rectangles. Cases to cover:
+ *
+ * geometry: normal, small (smaller than stroke-width), empty, inverted
+ * joint-type for the corners
+ */
+class StrokeRectGM : public skiagm::GM {
+public:
+ StrokeRectGM() {}
+
+protected:
+ virtual SkString onShortName() {
+ return SkString("strokerect");
+ }
+
+ virtual SkISize onISize() {
+ return SkISize::Make(1024, 740);
+ }
+
+ virtual void onDraw(SkCanvas* canvas) {
+ canvas->drawColor(SK_ColorWHITE);
+ canvas->translate(STROKE_WIDTH*3/2, STROKE_WIDTH*3/2);
+
+ SkPaint paint;
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setStrokeWidth(STROKE_WIDTH);
+
+ static const SkPaint::Join gJoins[] = {
+ SkPaint::kMiter_Join, SkPaint::kRound_Join, SkPaint::kBevel_Join
+ };
+
+ static const SkScalar W = 80;
+ static const SkScalar H = 80;
+ static const SkRect gRects[] = {
+ { 0, 0, W, H },
+ { W, 0, 0, H },
+ { 0, H, W, 0 },
+ { 0, 0, STROKE_WIDTH, H },
+ { 0, 0, W, STROKE_WIDTH },
+ { 0, 0, STROKE_WIDTH/2, STROKE_WIDTH/2 },
+ { 0, 0, W, 0 },
+ { 0, 0, 0, H },
+ { 0, 0, 0, 0 },
+ };
+
+ for (int doFill = 0; doFill <= 1; ++doFill) {
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gJoins); ++i) {
+ SkPaint::Join join = gJoins[i];
+ paint.setStrokeJoin(join);
+
+ SkAutoCanvasRestore acr(canvas, true);
+ for (size_t j = 0; j < SK_ARRAY_COUNT(gRects); ++j) {
+ const SkRect& r = gRects[j];
+
+ SkPath path, fillPath;
+ path.addRect(r);
+ paint.getFillPath(path, &fillPath);
+ draw_path(canvas, fillPath, r, join, doFill);
+
+ canvas->translate(W + 2 * STROKE_WIDTH, 0);
+ }
+ acr.restore();
+ canvas->translate(0, H + 2 * STROKE_WIDTH);
+ }
+ paint.setStyle(SkPaint::kStrokeAndFill_Style);
+ }
+ }
+
+private:
+ typedef GM INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+DEF_GM(return new StrokeRectGM;)
+
diff --git a/gm/tablecolorfilter.cpp b/gm/tablecolorfilter.cpp
index c8806f904d..49e342d002 100644
--- a/gm/tablecolorfilter.cpp
+++ b/gm/tablecolorfilter.cpp
@@ -15,7 +15,7 @@ static void make_bm0(SkBitmap* bm) {
int H = 120;
bm->setConfig(SkBitmap::kARGB_8888_Config, W, H);
bm->allocPixels();
- bm->eraseColor(0);
+ bm->eraseColor(SK_ColorTRANSPARENT);
SkCanvas canvas(*bm);
SkPaint paint;
@@ -34,7 +34,7 @@ static void make_bm1(SkBitmap* bm) {
int H = 120;
bm->setConfig(SkBitmap::kARGB_8888_Config, W, H);
bm->allocPixels();
- bm->eraseColor(0);
+ bm->eraseColor(SK_ColorTRANSPARENT);
SkCanvas canvas(*bm);
SkPaint paint;
diff --git a/gm/testimagefilters.cpp b/gm/testimagefilters.cpp
index 39b974b296..65d1be40ac 100644
--- a/gm/testimagefilters.cpp
+++ b/gm/testimagefilters.cpp
@@ -13,6 +13,8 @@
#include "SkBlurImageFilter.h"
#include "SkColorFilterImageFilter.h"
+#include "SkMergeImageFilter.h"
+#include "SkOffsetImageFilter.h"
#include "SkTestImageFilters.h"
#define FILTER_WIDTH SkIntToScalar(150)
@@ -24,7 +26,7 @@ static SkImageFilter* make2() {
SkColorFilter* cf = SkColorFilter::CreateModeFilter(SK_ColorBLUE,
SkXfermode::kSrcIn_Mode);
SkAutoUnref aur(cf);
- return new SkColorFilterImageFilter(cf);
+ return SkColorFilterImageFilter::Create(cf);
}
static SkImageFilter* make3() {
return new SkBlurImageFilter(8, 0);
@@ -56,7 +58,7 @@ static SkImageFilter* make6() {
SkColorFilter* cf = SkColorFilter::CreateModeFilter(0x880000FF,
SkXfermode::kSrcIn_Mode);
SkAutoUnref aur3(cf);
- SkImageFilter* blue = new SkColorFilterImageFilter(cf);
+ SkImageFilter* blue = SkColorFilterImageFilter::Create(cf);
SkAutoUnref aur4(blue);
return new SkMergeImageFilter(compose, blue);
@@ -73,7 +75,7 @@ static SkImageFilter* make7() {
SkColorFilter* cf = SkColorFilter::CreateModeFilter(0x880000FF,
SkXfermode::kSrcIn_Mode);
SkAutoUnref aur3(cf);
- SkImageFilter* blue = new SkColorFilterImageFilter(cf);
+ SkImageFilter* blue = SkColorFilterImageFilter::Create(cf);
SkAutoUnref aur4(blue);
return new SkMergeImageFilter(compose, blue);
diff --git a/gm/tests/inputs/different-pixels/8888/dashing2.png b/gm/tests/inputs/different-pixels/8888/dashing2.png
new file mode 100644
index 0000000000..3a0bc2e902
--- /dev/null
+++ b/gm/tests/inputs/different-pixels/8888/dashing2.png
Binary files differ
diff --git a/gm/tests/inputs/empty-dir/README b/gm/tests/inputs/empty-dir/README
new file mode 100644
index 0000000000..4d39134f2b
--- /dev/null
+++ b/gm/tests/inputs/empty-dir/README
@@ -0,0 +1 @@
+This directory intentionally left empty. Except for this file.
diff --git a/gm/tests/inputs/identical-bytes/8888/dashing2.png b/gm/tests/inputs/identical-bytes/8888/dashing2.png
new file mode 100644
index 0000000000..465c0199df
--- /dev/null
+++ b/gm/tests/inputs/identical-bytes/8888/dashing2.png
Binary files differ
diff --git a/gm/tests/inputs/identical-pixels/8888/dashing2.png b/gm/tests/inputs/identical-pixels/8888/dashing2.png
new file mode 100644
index 0000000000..2d05b01b01
--- /dev/null
+++ b/gm/tests/inputs/identical-pixels/8888/dashing2.png
Binary files differ
diff --git a/gm/tests/outputs/compared-against-different-pixels/output-expected/command_line b/gm/tests/outputs/compared-against-different-pixels/output-expected/command_line
new file mode 100644
index 0000000000..4253e730ef
--- /dev/null
+++ b/gm/tests/outputs/compared-against-different-pixels/output-expected/command_line
@@ -0,0 +1 @@
+out/Debug/gm --hierarchy --match dashing2 --config 8888 -r gm/tests/inputs/different-pixels --writeJsonSummary gm/tests/outputs/compared-against-different-pixels/output-actual/json-summary.txt -w gm/tests/outputs/compared-against-different-pixels/output-actual/images
diff --git a/gm/tests/outputs/compared-against-different-pixels/output-expected/images/8888/dashing2.png b/gm/tests/outputs/compared-against-different-pixels/output-expected/images/8888/dashing2.png
new file mode 100644
index 0000000000..465c0199df
--- /dev/null
+++ b/gm/tests/outputs/compared-against-different-pixels/output-expected/images/8888/dashing2.png
Binary files differ
diff --git a/gm/tests/outputs/compared-against-different-pixels/output-expected/json-summary.txt b/gm/tests/outputs/compared-against-different-pixels/output-expected/json-summary.txt
new file mode 100644
index 0000000000..179c60c162
--- /dev/null
+++ b/gm/tests/outputs/compared-against-different-pixels/output-expected/json-summary.txt
@@ -0,0 +1,17 @@
+{
+ "actual-results" : {
+ "failed" : {
+ "8888/dashing2" : {
+ "checksum" : 2675870163990933333
+ }
+ },
+ "failure-ignored" : null,
+ "succeeded" : null
+ },
+ "expected-results" : {
+ "8888/dashing2" : {
+ "checksums" : [ 15161495552186645995 ],
+ "ignore-failure" : false
+ }
+ }
+}
diff --git a/gm/tests/outputs/compared-against-different-pixels/output-expected/return_value b/gm/tests/outputs/compared-against-different-pixels/output-expected/return_value
new file mode 100644
index 0000000000..ace9d03621
--- /dev/null
+++ b/gm/tests/outputs/compared-against-different-pixels/output-expected/return_value
@@ -0,0 +1 @@
+255
diff --git a/gm/tests/outputs/compared-against-different-pixels/output-expected/stdout b/gm/tests/outputs/compared-against-different-pixels/output-expected/stdout
new file mode 100644
index 0000000000..3833cbe714
--- /dev/null
+++ b/gm/tests/outputs/compared-against-different-pixels/output-expected/stdout
@@ -0,0 +1,6 @@
+reading from gm/tests/inputs/different-pixels
+writing to gm/tests/outputs/compared-against-different-pixels/output-actual/images
+drawing... dashing2 [640 480]
+----- max pixel mismatch for 8888/dashing2 is 51
+Ran 1 tests: 0 passed, 1 failed, 0 missing reference images
+ 8888/dashing2 pixel_error 51
diff --git a/gm/tests/outputs/compared-against-empty-dir/output-expected/command_line b/gm/tests/outputs/compared-against-empty-dir/output-expected/command_line
new file mode 100644
index 0000000000..0ee96beddd
--- /dev/null
+++ b/gm/tests/outputs/compared-against-empty-dir/output-expected/command_line
@@ -0,0 +1 @@
+out/Debug/gm --hierarchy --match dashing2 --config 8888 -r gm/tests/inputs/empty-dir --writeJsonSummary gm/tests/outputs/compared-against-empty-dir/output-actual/json-summary.txt -w gm/tests/outputs/compared-against-empty-dir/output-actual/images
diff --git a/gm/tests/outputs/compared-against-empty-dir/output-expected/images/8888/dashing2.png b/gm/tests/outputs/compared-against-empty-dir/output-expected/images/8888/dashing2.png
new file mode 100644
index 0000000000..465c0199df
--- /dev/null
+++ b/gm/tests/outputs/compared-against-empty-dir/output-expected/images/8888/dashing2.png
Binary files differ
diff --git a/gm/tests/outputs/compared-against-empty-dir/output-expected/json-summary.txt b/gm/tests/outputs/compared-against-empty-dir/output-expected/json-summary.txt
new file mode 100644
index 0000000000..935ba5037d
--- /dev/null
+++ b/gm/tests/outputs/compared-against-empty-dir/output-expected/json-summary.txt
@@ -0,0 +1,17 @@
+{
+ "actual-results" : {
+ "failed" : null,
+ "failure-ignored" : {
+ "8888/dashing2" : {
+ "checksum" : 2675870163990933333
+ }
+ },
+ "succeeded" : null
+ },
+ "expected-results" : {
+ "8888/dashing2" : {
+ "checksums" : null,
+ "ignore-failure" : true
+ }
+ }
+}
diff --git a/gm/tests/outputs/compared-against-empty-dir/output-expected/return_value b/gm/tests/outputs/compared-against-empty-dir/output-expected/return_value
new file mode 100644
index 0000000000..573541ac97
--- /dev/null
+++ b/gm/tests/outputs/compared-against-empty-dir/output-expected/return_value
@@ -0,0 +1 @@
+0
diff --git a/gm/tests/outputs/compared-against-empty-dir/output-expected/stdout b/gm/tests/outputs/compared-against-empty-dir/output-expected/stdout
new file mode 100644
index 0000000000..436554d29e
--- /dev/null
+++ b/gm/tests/outputs/compared-against-empty-dir/output-expected/stdout
@@ -0,0 +1,5 @@
+reading from gm/tests/inputs/empty-dir
+writing to gm/tests/outputs/compared-against-empty-dir/output-actual/images
+drawing... dashing2 [640 480]
+FAILED to read gm/tests/inputs/empty-dir/8888/dashing2.png
+Ran 1 tests: 0 passed, 0 failed, 1 missing reference images
diff --git a/gm/tests/outputs/compared-against-identical-bytes/output-expected/command_line b/gm/tests/outputs/compared-against-identical-bytes/output-expected/command_line
new file mode 100644
index 0000000000..556498ab12
--- /dev/null
+++ b/gm/tests/outputs/compared-against-identical-bytes/output-expected/command_line
@@ -0,0 +1 @@
+out/Debug/gm --hierarchy --match dashing2 --config 8888 -r gm/tests/inputs/identical-bytes --writeJsonSummary gm/tests/outputs/compared-against-identical-bytes/output-actual/json-summary.txt -w gm/tests/outputs/compared-against-identical-bytes/output-actual/images
diff --git a/gm/tests/outputs/compared-against-identical-bytes/output-expected/images/8888/dashing2.png b/gm/tests/outputs/compared-against-identical-bytes/output-expected/images/8888/dashing2.png
new file mode 100644
index 0000000000..465c0199df
--- /dev/null
+++ b/gm/tests/outputs/compared-against-identical-bytes/output-expected/images/8888/dashing2.png
Binary files differ
diff --git a/gm/tests/outputs/compared-against-identical-bytes/output-expected/json-summary.txt b/gm/tests/outputs/compared-against-identical-bytes/output-expected/json-summary.txt
new file mode 100644
index 0000000000..cbb28e1172
--- /dev/null
+++ b/gm/tests/outputs/compared-against-identical-bytes/output-expected/json-summary.txt
@@ -0,0 +1,17 @@
+{
+ "actual-results" : {
+ "failed" : null,
+ "failure-ignored" : null,
+ "succeeded" : {
+ "8888/dashing2" : {
+ "checksum" : 2675870163990933333
+ }
+ }
+ },
+ "expected-results" : {
+ "8888/dashing2" : {
+ "checksums" : [ 2675870163990933333 ],
+ "ignore-failure" : false
+ }
+ }
+}
diff --git a/gm/tests/outputs/compared-against-identical-bytes/output-expected/return_value b/gm/tests/outputs/compared-against-identical-bytes/output-expected/return_value
new file mode 100644
index 0000000000..573541ac97
--- /dev/null
+++ b/gm/tests/outputs/compared-against-identical-bytes/output-expected/return_value
@@ -0,0 +1 @@
+0
diff --git a/gm/tests/outputs/compared-against-identical-bytes/output-expected/stdout b/gm/tests/outputs/compared-against-identical-bytes/output-expected/stdout
new file mode 100644
index 0000000000..301f17f5e7
--- /dev/null
+++ b/gm/tests/outputs/compared-against-identical-bytes/output-expected/stdout
@@ -0,0 +1,4 @@
+reading from gm/tests/inputs/identical-bytes
+writing to gm/tests/outputs/compared-against-identical-bytes/output-actual/images
+drawing... dashing2 [640 480]
+Ran 1 tests: 1 passed, 0 failed, 0 missing reference images
diff --git a/gm/tests/outputs/compared-against-identical-pixels/output-expected/command_line b/gm/tests/outputs/compared-against-identical-pixels/output-expected/command_line
new file mode 100644
index 0000000000..ecdef271da
--- /dev/null
+++ b/gm/tests/outputs/compared-against-identical-pixels/output-expected/command_line
@@ -0,0 +1 @@
+out/Debug/gm --hierarchy --match dashing2 --config 8888 -r gm/tests/inputs/identical-pixels --writeJsonSummary gm/tests/outputs/compared-against-identical-pixels/output-actual/json-summary.txt -w gm/tests/outputs/compared-against-identical-pixels/output-actual/images
diff --git a/gm/tests/outputs/compared-against-identical-pixels/output-expected/images/8888/dashing2.png b/gm/tests/outputs/compared-against-identical-pixels/output-expected/images/8888/dashing2.png
new file mode 100644
index 0000000000..465c0199df
--- /dev/null
+++ b/gm/tests/outputs/compared-against-identical-pixels/output-expected/images/8888/dashing2.png
Binary files differ
diff --git a/gm/tests/outputs/compared-against-identical-pixels/output-expected/json-summary.txt b/gm/tests/outputs/compared-against-identical-pixels/output-expected/json-summary.txt
new file mode 100644
index 0000000000..cbb28e1172
--- /dev/null
+++ b/gm/tests/outputs/compared-against-identical-pixels/output-expected/json-summary.txt
@@ -0,0 +1,17 @@
+{
+ "actual-results" : {
+ "failed" : null,
+ "failure-ignored" : null,
+ "succeeded" : {
+ "8888/dashing2" : {
+ "checksum" : 2675870163990933333
+ }
+ }
+ },
+ "expected-results" : {
+ "8888/dashing2" : {
+ "checksums" : [ 2675870163990933333 ],
+ "ignore-failure" : false
+ }
+ }
+}
diff --git a/gm/tests/outputs/compared-against-identical-pixels/output-expected/return_value b/gm/tests/outputs/compared-against-identical-pixels/output-expected/return_value
new file mode 100644
index 0000000000..573541ac97
--- /dev/null
+++ b/gm/tests/outputs/compared-against-identical-pixels/output-expected/return_value
@@ -0,0 +1 @@
+0
diff --git a/gm/tests/outputs/compared-against-identical-pixels/output-expected/stdout b/gm/tests/outputs/compared-against-identical-pixels/output-expected/stdout
new file mode 100644
index 0000000000..00868ac5b1
--- /dev/null
+++ b/gm/tests/outputs/compared-against-identical-pixels/output-expected/stdout
@@ -0,0 +1,4 @@
+reading from gm/tests/inputs/identical-pixels
+writing to gm/tests/outputs/compared-against-identical-pixels/output-actual/images
+drawing... dashing2 [640 480]
+Ran 1 tests: 1 passed, 0 failed, 0 missing reference images
diff --git a/gm/tests/rebaseline.sh b/gm/tests/rebaseline.sh
new file mode 100755
index 0000000000..77f8711e18
--- /dev/null
+++ b/gm/tests/rebaseline.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+
+# Rebaseline the outputs/*/output-expected/ subdirectories used by the
+# gm self-tests.
+# Use with caution: are you sure the new results are actually correct?
+#
+# YOU MUST RE-RUN THIS UNTIL THE SELF-TESTS SUCCEED!
+# (It takes one run for each call to gm_test in run.sh)
+
+# cd into the gm self-test dir
+cd $(dirname $0)
+
+./run.sh
+
+# Delete all the expected output files
+EXPECTED_FILES=$(find outputs/*/output-expected -type f | grep -v /\.svn/)
+for EXPECTED_FILE in $EXPECTED_FILES; do
+ rm $EXPECTED_FILE
+done
+
+# Copy all the actual output files into the "expected" directories,
+# creating new subdirs as we go.
+ACTUAL_FILES=$(find outputs/*/output-actual -type f | grep -v /\.svn/)
+for ACTUAL_FILE in $ACTUAL_FILES; do
+ EXPECTED_FILE=${ACTUAL_FILE//actual/expected}
+ mkdir -p $(dirname $EXPECTED_FILE)
+ cp $ACTUAL_FILE $EXPECTED_FILE
+done
+
+# "svn add" any newly expected files/dirs, and "svn rm" any that are gone now
+FILES=$(svn stat outputs/*/output-expected | grep ^\? | awk '{print $2}')
+for FILE in $FILES; do
+ svn add $FILE
+done
+FILES=$(svn stat outputs/*/output-expected | grep ^\! | awk '{print $2}')
+for FILE in $FILES; do
+ svn rm $FILE
+done
diff --git a/gm/tests/run.sh b/gm/tests/run.sh
new file mode 100755
index 0000000000..2bb9441d46
--- /dev/null
+++ b/gm/tests/run.sh
@@ -0,0 +1,79 @@
+#!/bin/bash
+
+# Self-tests for gm, based on tools/tests/run.sh
+#
+# These tests are run by the Skia_PerCommit_House_Keeping bot at every commit,
+# so make sure that they still pass when you make changes to gm!
+#
+# TODO: Even though these tests are passing on the Skia_PerCommit_House_Keeping
+# bot (which runs on Linux), they fail when I run them on my Mac.
+# Ideally, these tests should pass on all development platforms...
+# otherwise, how can developers be expected to test them before committing a
+# change?
+
+# cd into .../trunk so all the paths will work
+cd $(dirname $0)/../..
+
+# TODO(epoger): make it look in Release and/or Debug
+GM_BINARY=out/Debug/gm
+
+# Compare contents of all files within directories $1 and $2,
+# EXCEPT for any dotfiles.
+# If there are any differences, a description is written to stdout and
+# we exit with a nonzero return value.
+# Otherwise, we write nothing to stdout and return.
+function compare_directories {
+ if [ $# != 2 ]; then
+ echo "compare_directories requires exactly 2 parameters, got $#"
+ exit 1
+ fi
+ diff -r --exclude=.* $1 $2
+ if [ $? != 0 ]; then
+ echo "failed in: compare_directories $1 $2"
+ exit 1
+ fi
+}
+
+# Run gm...
+# - with the arguments in $1
+# - writing resulting images into $2/output-actual/images
+# - writing stdout into $2/output-actual/stdout
+# - writing json summary into $2/output-actual/json-summary.txt
+# - writing return value into $2/output-actual/return_value
+# Then compare all of those against $2/output-expected .
+function gm_test {
+ if [ $# != 2 ]; then
+ echo "gm_test requires exactly 2 parameters, got $#"
+ exit 1
+ fi
+ GM_ARGS="$1"
+ ACTUAL_OUTPUT_DIR="$2/output-actual"
+ EXPECTED_OUTPUT_DIR="$2/output-expected"
+
+ rm -rf $ACTUAL_OUTPUT_DIR
+ mkdir -p $ACTUAL_OUTPUT_DIR
+ COMMAND="$GM_BINARY $GM_ARGS --writeJsonSummary $ACTUAL_OUTPUT_DIR/json-summary.txt -w $ACTUAL_OUTPUT_DIR/images"
+ echo "$COMMAND" >$ACTUAL_OUTPUT_DIR/command_line
+ $COMMAND &>$ACTUAL_OUTPUT_DIR/stdout
+ echo $? >$ACTUAL_OUTPUT_DIR/return_value
+
+ compare_directories $EXPECTED_OUTPUT_DIR $ACTUAL_OUTPUT_DIR
+}
+
+GM_TESTDIR=gm/tests
+GM_INPUTS=$GM_TESTDIR/inputs
+GM_OUTPUTS=$GM_TESTDIR/outputs
+
+# Compare generated image against an input image file with identical bytes.
+gm_test "--hierarchy --match dashing2 --config 8888 -r $GM_INPUTS/identical-bytes" "$GM_OUTPUTS/compared-against-identical-bytes"
+
+# Compare generated image against an input image file with identical pixels but different PNG encoding.
+gm_test "--hierarchy --match dashing2 --config 8888 -r $GM_INPUTS/identical-pixels" "$GM_OUTPUTS/compared-against-identical-pixels"
+
+# Compare generated image against an input image file with different pixels.
+gm_test "--hierarchy --match dashing2 --config 8888 -r $GM_INPUTS/different-pixels" "$GM_OUTPUTS/compared-against-different-pixels"
+
+# Compare generated image against an empty "expected image" dir.
+gm_test "--hierarchy --match dashing2 --config 8888 -r $GM_INPUTS/empty-dir" "$GM_OUTPUTS/compared-against-empty-dir"
+
+echo "All tests passed."
diff --git a/gm/texdata.cpp b/gm/texdata.cpp
index 09721acc13..e36feef0cb 100644
--- a/gm/texdata.cpp
+++ b/gm/texdata.cpp
@@ -101,7 +101,7 @@ protected:
GrPaint paint;
paint.setBlendFunc(kOne_GrBlendCoeff, kISA_GrBlendCoeff);
- GrMatrix vm;
+ SkMatrix vm;
if (i) {
vm.setRotate(90 * SK_Scalar1,
S * SK_Scalar1,
@@ -110,11 +110,11 @@ protected:
vm.reset();
}
ctx->setMatrix(vm);
- GrMatrix tm;
+ SkMatrix tm;
tm = vm;
tm.postIDiv(2*S, 2*S);
- paint.colorSampler(0)->setCustomStage(SkNEW_ARGS(GrSingleTextureEffect,
- (texture)), tm)->unref();
+ paint.colorStage(0)->setEffect(SkNEW_ARGS(GrSingleTextureEffect,
+ (texture, tm)))->unref();
ctx->drawRect(paint, GrRect::MakeWH(2*S, 2*S));
diff --git a/gm/tilemodes.cpp b/gm/tilemodes.cpp
index 08bcb79f65..d51fce2cf6 100644
--- a/gm/tilemodes.cpp
+++ b/gm/tilemodes.cpp
@@ -19,12 +19,10 @@
#include "SkUnitMappers.h"
#include "SkBlurDrawLooper.h"
-namespace skiagm {
-
static void makebm(SkBitmap* bm, SkBitmap::Config config, int w, int h) {
bm->setConfig(config, w, h);
bm->allocPixels();
- bm->eraseColor(0);
+ bm->eraseColor(SK_ColorTRANSPARENT);
SkCanvas canvas(*bm);
SkPoint pts[] = { { 0, 0 }, { SkIntToScalar(w), SkIntToScalar(h)} };
@@ -60,15 +58,12 @@ static const SkBitmap::Config gConfigs[] = {
static const int gWidth = 32;
static const int gHeight = 32;
-class TilingGM : public GM {
+class TilingGM : public skiagm::GM {
SkBlurDrawLooper fLooper;
public:
TilingGM()
: fLooper(SkIntToScalar(1), SkIntToScalar(2), SkIntToScalar(2),
0x88000000) {
- for (size_t i = 0; i < SK_ARRAY_COUNT(gConfigs); i++) {
- makebm(&fTexture[i], gConfigs[i], gWidth, gHeight);
- }
}
SkBitmap fTexture[SK_ARRAY_COUNT(gConfigs)];
@@ -78,9 +73,15 @@ protected:
return SkString("tilemodes");
}
- SkISize onISize() { return make_isize(880, 560); }
+ SkISize onISize() { return SkISize::Make(880, 560); }
- virtual void onDraw(SkCanvas* canvas) {
+ virtual void onOnceBeforeDraw() SK_OVERRIDE {
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gConfigs); i++) {
+ makebm(&fTexture[i], gConfigs[i], gWidth, gHeight);
+ }
+ }
+
+ virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE {
SkRect r = { 0, 0, SkIntToScalar(gWidth*2), SkIntToScalar(gHeight*2) };
@@ -145,13 +146,124 @@ protected:
}
private:
- typedef GM INHERITED;
+ typedef skiagm::GM INHERITED;
};
-//////////////////////////////////////////////////////////////////////////////
+static SkShader* make_bm(SkShader::TileMode tx, SkShader::TileMode ty) {
+ SkBitmap bm;
+ makebm(&bm, SkBitmap::kARGB_8888_Config, gWidth, gHeight);
+ return SkShader::CreateBitmapShader(bm, tx, ty);
+}
-static GM* MyFactory(void*) { return new TilingGM; }
-static GMRegistry reg(MyFactory);
+static SkShader* make_grad(SkShader::TileMode tx, SkShader::TileMode ty) {
+ SkPoint pts[] = { { 0, 0 }, { SkIntToScalar(gWidth), SkIntToScalar(gHeight)} };
+ SkPoint center = { SkIntToScalar(gWidth)/2, SkIntToScalar(gHeight)/2 };
+ SkScalar rad = SkIntToScalar(gWidth)/2;
+ SkColor colors[] = { 0xFFFF0000, 0xFF0044FF };
+
+ int index = (int)ty;
+ switch (index % 3) {
+ case 0:
+ return SkGradientShader::CreateLinear(pts, colors, NULL, SK_ARRAY_COUNT(colors), tx);
+ case 1:
+ return SkGradientShader::CreateRadial(center, rad, colors, NULL, SK_ARRAY_COUNT(colors), tx);
+ case 2:
+ return SkGradientShader::CreateSweep(center.fX, center.fY, colors, NULL, SK_ARRAY_COUNT(colors));
+ }
+ return NULL;
}
+static SkShader* make_radial(SkShader::TileMode tx, SkShader::TileMode ty) {
+ SkPoint center = { SkIntToScalar(gWidth)/2, SkIntToScalar(gHeight)/2 };
+ SkScalar rad = SkIntToScalar(gWidth)/2;
+ SkColor colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE };
+
+ return SkGradientShader::CreateRadial(center, rad, colors, NULL, SK_ARRAY_COUNT(colors), tx);
+}
+
+typedef SkShader* (*ShaderProc)(SkShader::TileMode, SkShader::TileMode);
+
+class Tiling2GM : public skiagm::GM {
+ ShaderProc fProc;
+ SkString fName;
+public:
+ Tiling2GM(ShaderProc proc, const char name[]) : fProc(proc) {
+ fName.printf("tilemode_%s", name);
+ }
+
+protected:
+ SkString onShortName() {
+ return fName;
+ }
+
+ SkISize onISize() { return SkISize::Make(880, 560); }
+
+ virtual void onDraw(SkCanvas* canvas) {
+ canvas->scale(SkIntToScalar(3)/2, SkIntToScalar(3)/2);
+
+ const SkScalar w = SkIntToScalar(gWidth);
+ const SkScalar h = SkIntToScalar(gHeight);
+ SkRect r = { -w, -h, w*2, h*2 };
+
+ static const SkShader::TileMode gModes[] = {
+ SkShader::kClamp_TileMode, SkShader::kRepeat_TileMode, SkShader::kMirror_TileMode
+ };
+ static const char* gModeNames[] = {
+ "Clamp", "Repeat", "Mirror"
+ };
+
+ SkScalar y = SkIntToScalar(24);
+ SkScalar x = SkIntToScalar(66);
+
+ SkPaint p;
+ p.setAntiAlias(true);
+ p.setTextAlign(SkPaint::kCenter_Align);
+
+ for (size_t kx = 0; kx < SK_ARRAY_COUNT(gModes); kx++) {
+ SkString str(gModeNames[kx]);
+ canvas->drawText(str.c_str(), str.size(), x + r.width()/2, y, p);
+ x += r.width() * 4 / 3;
+ }
+
+ y += SkIntToScalar(16) + h;
+ p.setTextAlign(SkPaint::kRight_Align);
+
+ for (size_t ky = 0; ky < SK_ARRAY_COUNT(gModes); ky++) {
+ x = SkIntToScalar(16) + w;
+
+ SkString str(gModeNames[ky]);
+ canvas->drawText(str.c_str(), str.size(), x, y + h/2, p);
+
+ x += SkIntToScalar(50);
+ for (size_t kx = 0; kx < SK_ARRAY_COUNT(gModes); kx++) {
+ SkPaint paint;
+ paint.setShader(fProc(gModes[kx], gModes[ky]))->unref();
+
+ canvas->save();
+ canvas->translate(x, y);
+ canvas->drawRect(r, paint);
+ canvas->restore();
+
+ x += r.width() * 4 / 3;
+ }
+ y += r.height() * 4 / 3;
+ }
+ }
+
+private:
+ typedef skiagm::GM INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static skiagm::GM* MyFactory(void*) { return new TilingGM; }
+static skiagm::GMRegistry reg(MyFactory);
+
+static skiagm::GM* MyFactory2(void*) { return new Tiling2GM(make_bm, "bitmap"); }
+static skiagm::GMRegistry reg2(MyFactory2);
+
+static skiagm::GM* MyFactory3(void*) { return new Tiling2GM(make_grad, "gradient"); }
+static skiagm::GMRegistry reg3(MyFactory3);
+
+
diff --git a/gm/tinybitmap.cpp b/gm/tinybitmap.cpp
index 3b1006c189..26bf25e665 100644
--- a/gm/tinybitmap.cpp
+++ b/gm/tinybitmap.cpp
@@ -32,11 +32,9 @@ static SkBitmap make_bitmap() {
}
class TinyBitmapGM : public GM {
- SkBitmap fBM;
public:
TinyBitmapGM() {
this->setBGColor(0xFFDDDDDD);
- fBM = make_bitmap();
}
protected:
@@ -47,8 +45,9 @@ protected:
virtual SkISize onISize() { return make_isize(100, 100); }
virtual void onDraw(SkCanvas* canvas) {
+ SkBitmap bm = make_bitmap();
SkShader* s =
- SkShader::CreateBitmapShader(fBM, SkShader::kRepeat_TileMode,
+ SkShader::CreateBitmapShader(bm, SkShader::kRepeat_TileMode,
SkShader::kMirror_TileMode);
SkPaint paint;
paint.setAlpha(0x80);
diff --git a/gm/xfermodes.cpp b/gm/xfermodes.cpp
index 22b6edd918..8934bc7323 100644
--- a/gm/xfermodes.cpp
+++ b/gm/xfermodes.cpp
@@ -15,7 +15,7 @@ namespace skiagm {
static void make_bitmaps(int w, int h, SkBitmap* src, SkBitmap* dst) {
src->setConfig(SkBitmap::kARGB_8888_Config, w, h);
src->allocPixels();
- src->eraseColor(0);
+ src->eraseColor(SK_ColorTRANSPARENT);
SkPaint p;
p.setAntiAlias(true);
@@ -33,7 +33,7 @@ static void make_bitmaps(int w, int h, SkBitmap* src, SkBitmap* dst) {
dst->setConfig(SkBitmap::kARGB_8888_Config, w, h);
dst->allocPixels();
- dst->eraseColor(0);
+ dst->eraseColor(SK_ColorTRANSPARENT);
{
SkCanvas c(*dst);
diff --git a/gyp/SampleApp.gyp b/gyp/SampleApp.gyp
index c33c67ffe3..715ef3b2f2 100644
--- a/gyp/SampleApp.gyp
+++ b/gyp/SampleApp.gyp
@@ -51,6 +51,7 @@
'../samplecode/SampleEmboss.cpp',
'../samplecode/SampleEmptyPath.cpp',
'../samplecode/SampleEncode.cpp',
+ '../samplecode/SampleFatBits.cpp',
'../samplecode/SampleFillType.cpp',
'../samplecode/SampleFilter.cpp',
'../samplecode/SampleFilter2.cpp',
@@ -70,7 +71,6 @@
'../samplecode/SampleMovie.cpp',
'../samplecode/SampleOvalTest.cpp',
'../samplecode/SampleOverflow.cpp',
- '../samplecode/SamplePageFlip.cpp',
'../samplecode/SamplePatch.cpp',
'../samplecode/SamplePath.cpp',
'../samplecode/SamplePathClip.cpp',
@@ -155,7 +155,6 @@
'sources!': [
# require UNIX functions
'../samplecode/SampleEncode.cpp',
- '../samplecode/SamplePageFlip.cpp',
],
}],
[ 'skia_os == "mac"', {
@@ -281,7 +280,7 @@
'experimental.gyp:experimental',
],
'dependencies': [
- 'android_system.gyp:SampleAppAndroid',
+ 'android_deps.gyp:Android_SampleApp',
],
}],
[ 'skia_gpu == 1', {
@@ -289,6 +288,11 @@
'../src/gpu', # To pull gl/GrGLUtil.h
],
}],
+ [ 'skia_os == "nacl"', {
+ 'sources': [
+ '../../nacl/src/nacl_sample.cpp',
+ ],
+ }],
],
'msvs_settings': {
'VCLinkerTool': {
diff --git a/gyp/all.gyp b/gyp/all.gyp
deleted file mode 100644
index 37844bb224..0000000000
--- a/gyp/all.gyp
+++ /dev/null
@@ -1,41 +0,0 @@
-# Creates a Makefile that is capable of building all executable targets.
-#
-# To build on Linux:
-# ./gyp_skia && make all
-#
-# Building on other platforms not tested yet.
-#
-
-#
-#
-#
-#
-#
-# THIS IS DEPRECATED IN FAVOR OF trunk/skia.gyp !!!
-# Questions? Contact epoger@google.com
-#
-#
-#
-#
-
-{
- 'targets': [
- {
- 'target_name': 'all',
- 'type': 'none',
- 'dependencies': [
- 'bench.gyp:bench',
- 'gm.gyp:gm',
- 'SampleApp.gyp:SampleApp',
- 'tests.gyp:tests',
- 'tools.gyp:tools',
- ],
- },
- ],
-}
-
-# Local Variables:
-# tab-width:2
-# indent-tabs-mode:nil
-# End:
-# vim: set expandtab tabstop=2 shiftwidth=2:
diff --git a/gyp/android_system.gyp b/gyp/android_system.gyp
index a2a9a0ab55..57f973a43f 100644
--- a/gyp/android_system.gyp
+++ b/gyp/android_system.gyp
@@ -15,6 +15,6 @@
# variable expansion step for gyp happens after the includes are processed.
{
'includes': [
- '../../android/gyp/android.gypi',
+ '../../android/gyp/skia_android.gypi',
],
}
diff --git a/gyp/apptype_console.gypi b/gyp/apptype_console.gypi
index 80e925d9de..a30619373b 100644
--- a/gyp/apptype_console.gypi
+++ b/gyp/apptype_console.gypi
@@ -17,6 +17,11 @@
'android_deps.gyp:Android_EntryPoint',
],
}],
+ [ 'skia_os == "nacl"', {
+ 'dependencies': [
+ 'nacl.gyp:nacl_interface',
+ ],
+ }],
['skia_os == "ios"', {
'target_conditions': [
['_type == "executable"', {
diff --git a/gyp/bench.gypi b/gyp/bench.gypi
index 6f630d6345..dbb30b6fff 100644
--- a/gyp/bench.gypi
+++ b/gyp/bench.gypi
@@ -19,7 +19,9 @@
'../bench/GradientBench.cpp',
'../bench/GrMemoryPoolBench.cpp',
'../bench/InterpBench.cpp',
+ '../bench/LineBench.cpp',
'../bench/MathBench.cpp',
+ '../bench/Matrix44Bench.cpp',
'../bench/MatrixBench.cpp',
'../bench/MatrixConvolutionBench.cpp',
'../bench/MemoryBench.cpp',
diff --git a/gyp/common.gypi b/gyp/common.gypi
index 06db4a8823..37fb2f8f87 100644
--- a/gyp/common.gypi
+++ b/gyp/common.gypi
@@ -17,15 +17,21 @@
# situations, like building for iOS on a Mac.
'variables': {
'conditions': [
- ['skia_os != OS and not (skia_os == "ios" and OS == "mac")',
- {'error': '<!(Cannot build with skia_os=<(skia_os) on OS=<(OS))'}],
- ['skia_mesa and skia_os not in ["mac", "linux"]',
- {'error': '<!(skia_mesa=1 only supported with skia_os="mac" or "linux".)'}],
- ['skia_angle and not skia_os == "win"',
- {'error': '<!(skia_angle=1 only supported with skia_os="win".)'
+ [ 'skia_os != OS and not ((skia_os == "ios" and OS == "mac") or \
+ (skia_os == "nacl" and OS == "linux"))', {
+ 'error': '<!(Cannot build with skia_os=<(skia_os) on OS=<(OS))',
}],
- ['skia_arch_width != 32 and skia_arch_width != 64',
- {'error': '<!(skia_arch_width can only be 32 or 64 bits not <(skia_arch_width) bits)'
+ [ 'skia_mesa and skia_os not in ["mac", "linux"]', {
+ 'error': '<!(skia_mesa=1 only supported with skia_os="mac" or "linux".)',
+ }],
+ [ 'skia_angle and not skia_os == "win"', {
+ 'error': '<!(skia_angle=1 only supported with skia_os="win".)',
+ }],
+ [ 'skia_arch_width != 32 and skia_arch_width != 64', {
+ 'error': '<!(skia_arch_width can only be 32 or 64 bits not <(skia_arch_width) bits)',
+ }],
+ [ 'skia_os == "nacl" and OS != "linux"', {
+ 'error': '<!(Skia NaCl build only currently supported on Linux.)',
}],
],
},
diff --git a/gyp/common_conditions.gypi b/gyp/common_conditions.gypi
index da94aa4766..332d342be2 100644
--- a/gyp/common_conditions.gypi
+++ b/gyp/common_conditions.gypi
@@ -1,6 +1,11 @@
# conditions used in both common.gypi and skia.gyp in chromium
#
{
+ 'defines': [
+ 'SK_ALLOW_STATIC_GLOBAL_INITIALIZERS=<(skia_static_initializers)',
+# 'SK_SUPPORT_HINTING_SCALE_FACTOR',
+ 'SK_REDEFINE_ROOT2OVER2_TO_MAKE_ARCTOS_CONVEX',
+ ],
'conditions' : [
['skia_gpu == 1',
{
@@ -93,7 +98,7 @@
},
],
- ['skia_os in ["linux", "freebsd", "openbsd", "solaris"]',
+ ['skia_os in ["linux", "freebsd", "openbsd", "solaris", "nacl"]',
{
'defines': [
'SK_SAMPLES_FOR_X',
@@ -120,6 +125,11 @@
'-Wno-c++11-extensions'
],
'conditions' : [
+ ['skia_warnings_as_errors == 1', {
+ 'cflags': [
+ '-Werror',
+ ],
+ }],
['skia_arch_width == 64', {
'cflags': [
'-m64',
@@ -136,9 +146,23 @@
'-m32',
],
}],
- ],
- 'include_dirs' : [
- '/usr/include/freetype2',
+ [ 'skia_os == "nacl"', {
+ 'defines': [
+ 'SK_BUILD_FOR_NACL',
+ ],
+ 'link_settings': {
+ 'libraries': [
+ '-lppapi',
+ '-lppapi_cpp',
+ '-lnosys',
+ '-pthread',
+ ],
+ },
+ }, { # skia_os != "nacl"
+ 'include_dirs' : [
+ '/usr/include/freetype2',
+ ],
+ }],
],
},
],
@@ -271,6 +295,14 @@
'<(android_base)/toolchains/<(android_toolchain)/sysroot/usr/include',
],
'conditions': [
+ [ 'skia_warnings_as_errors == 1', {
+ 'cflags': [
+ '-Werror',
+ ],
+ }],
+ [ 'skia_profile_enabled == 1', {
+ 'cflags': ['-g', '-fno-omit-frame-pointer', '-marm', '-mapcs'],
+ }],
[ 'skia_arch_type == "arm"', {
'ldflags': [
'-Wl',
@@ -316,7 +348,7 @@
# static initializers if we're using a pthread-compatible thread interface.
[ 'skia_os != "win"', {
'defines': [
- 'SK_USE_POSIX_THREADS'
+ 'SK_USE_POSIX_THREADS',
],
}],
], # end 'conditions'
diff --git a/gyp/common_variables.gypi b/gyp/common_variables.gypi
index 3d19e62094..042340b449 100644
--- a/gyp/common_variables.gypi
+++ b/gyp/common_variables.gypi
@@ -81,11 +81,15 @@
'skia_scalar%': 'float',
'skia_mesa%': 0,
'skia_nv_path_rendering%': 0,
+ 'skia_texture_cache_mb_limit%': 0,
'skia_angle%': 0,
'skia_directwrite%': 0,
- 'skia_nacl%': 0,
'skia_gpu%': 1,
'skia_osx_sdkroot%': 'macosx',
+ 'skia_profile_enabled%': 0,
+ # Note: This is currently only turned on for linux and android.
+ # TODO: Turn on for Win and Mac as well.
+ 'skia_warnings_as_errors%': 0,
},
# Re-define all variables defined within the level-2 'variables' dict,
@@ -97,13 +101,15 @@
'skia_scalar%': '<(skia_scalar)',
'skia_mesa%': '<(skia_mesa)',
'skia_nv_path_rendering%': '<(skia_nv_path_rendering)',
+ 'skia_texture_cache_mb_limit%': '<(skia_texture_cache_mb_limit)',
'skia_angle%': '<(skia_angle)',
'skia_arch_width%': '<(skia_arch_width)',
'skia_arch_type%': '<(skia_arch_type)',
'skia_directwrite%': '<(skia_directwrite)',
- 'skia_nacl%': '<(skia_nacl)',
'skia_gpu%': '<(skia_gpu)',
'skia_osx_sdkroot%': '<(skia_osx_sdkroot)',
+ 'skia_profile_enabled%': '<(skia_profile_enabled)',
+ 'skia_warnings_as_errors%': '<(skia_warnings_as_errors)',
'skia_static_initializers%': '<(skia_static_initializers)',
'ios_sdk_version%': '6.0',
diff --git a/gyp/core.gyp b/gyp/core.gyp
index 228fd06e56..14b14b04fb 100644
--- a/gyp/core.gyp
+++ b/gyp/core.gyp
@@ -83,7 +83,7 @@
}],
[ 'skia_os == "android"', {
'dependencies': [
- 'android_deps.gyp:ft2',
+ 'freetype.gyp:freetype',
],
}],
[ 'skia_os == "android" and skia_arch_type == "arm" and armv7 == 1', {
diff --git a/gyp/core.gypi b/gyp/core.gypi
index d8f1715d93..483064596a 100644
--- a/gyp/core.gypi
+++ b/gyp/core.gypi
@@ -136,6 +136,7 @@
'<(skia_src_path)/core/SkRegion.cpp',
'<(skia_src_path)/core/SkRegionPriv.h',
'<(skia_src_path)/core/SkRegion_path.cpp',
+ '<(skia_src_path)/core/SkRRect.cpp',
'<(skia_src_path)/core/SkRTree.h',
'<(skia_src_path)/core/SkRTree.cpp',
'<(skia_src_path)/core/SkScalar.cpp',
@@ -157,13 +158,18 @@
'<(skia_src_path)/core/SkString.cpp',
'<(skia_src_path)/core/SkStroke.h',
'<(skia_src_path)/core/SkStroke.cpp',
+ '<(skia_src_path)/core/SkStrokeRec.cpp',
'<(skia_src_path)/core/SkStrokerPriv.cpp',
'<(skia_src_path)/core/SkStrokerPriv.h',
+ '<(skia_src_path)/core/SkTemplatesPriv.h',
'<(skia_src_path)/core/SkTextFormatParams.h',
+ '<(skia_src_path)/core/SkTileGrid.cpp',
+ '<(skia_src_path)/core/SkTileGrid.h',
+ '<(skia_src_path)/core/SkTileGridPicture.cpp',
+ '<(skia_src_path)/core/SkTLList.h',
'<(skia_src_path)/core/SkTLS.cpp',
'<(skia_src_path)/core/SkTSearch.cpp',
'<(skia_src_path)/core/SkTSort.h',
- '<(skia_src_path)/core/SkTemplatesPriv.h',
'<(skia_src_path)/core/SkTypeface.cpp',
'<(skia_src_path)/core/SkTypefaceCache.cpp',
'<(skia_src_path)/core/SkTypefaceCache.h',
@@ -240,16 +246,19 @@
'<(skia_include_path)/core/SkRect.h',
'<(skia_include_path)/core/SkRefCnt.h',
'<(skia_include_path)/core/SkRegion.h',
+ '<(skia_include_path)/core/SkRRect.h',
'<(skia_include_path)/core/SkScalar.h',
'<(skia_include_path)/core/SkScalarCompare.h',
'<(skia_include_path)/core/SkShader.h',
'<(skia_include_path)/core/SkStream.h',
'<(skia_include_path)/core/SkString.h',
+ '<(skia_include_path)/core/SkStrokeRec.h',
'<(skia_include_path)/core/SkTArray.h',
'<(skia_include_path)/core/SkTDArray.h',
'<(skia_include_path)/core/SkTDStack.h',
'<(skia_include_path)/core/SkTDict.h',
- '<(skia_include_path)/core/SkTDLinkedList.h',
+ '<(skia_include_path)/core/SkTInternalLList.h',
+ '<(skia_include_path)/core/SkTileGridPicture.h',
'<(skia_include_path)/core/SkTRegistry.h',
'<(skia_include_path)/core/SkTScopedPtr.h',
'<(skia_include_path)/core/SkTSearch.h',
diff --git a/gyp/debugger.gyp b/gyp/debugger.gyp
index 35782237f6..5c760db12a 100644
--- a/gyp/debugger.gyp
+++ b/gyp/debugger.gyp
@@ -81,6 +81,8 @@
'../debugger', # To pull SkDebugger.h
'../debugger/QT', # For all the QT UI Goodies
'../src/gpu', # To pull gl/GrGLUtil.h
+ '../bench',
+ '../tools',
'<@(qt_includes)',
],
'sources': [
@@ -106,6 +108,8 @@
'../debugger/QT/SkGLWidget.cpp',
'../debugger/QT/SkRasterWidget.h',
'../debugger/QT/SkRasterWidget.cpp',
+ '../debugger/QT/SkImageWidget.h',
+ '../debugger/QT/SkImageWidget.cpp',
# To update this file edit SkIcons.qrc and rerun rcc to generate cpp
'../debugger/QT/qrc_SkIcons.cpp',
@@ -116,12 +120,15 @@
'<(moc_gen_dir)/moc_SkInspectorWidget.cpp',
'<(moc_gen_dir)/moc_SkSettingsWidget.cpp',
'<(moc_gen_dir)/moc_SkRasterWidget.cpp',
+ '<(moc_gen_dir)/moc_SkImageWidget.cpp',
'<(moc_gen_dir)/moc_SkGLWidget.cpp',
],
'dependencies': [
'skia_base_libs.gyp:skia_base_libs',
'images.gyp:images',
'effects.gyp:effects',
+ 'bench.gyp:bench_timer',
+ 'tools.gyp:picture_renderer',
'debugger_mocs',
],
'link_settings': {
@@ -139,6 +146,7 @@
'<(moc_src_dir)/SkInspectorWidget.h',
'<(moc_src_dir)/SkSettingsWidget.h',
'<(moc_src_dir)/SkRasterWidget.h',
+ '<(moc_src_dir)/SkImageWidget.h',
'<(moc_src_dir)/SkGLWidget.h',
],
'rules': [
diff --git a/gyp/effects.gypi b/gyp/effects.gypi
index aef7f2c126..d981cd1e01 100644
--- a/gyp/effects.gypi
+++ b/gyp/effects.gypi
@@ -34,7 +34,9 @@
'<(skia_src_path)/effects/SkLayerRasterizer.cpp',
'<(skia_src_path)/effects/SkLightingImageFilter.cpp',
'<(skia_src_path)/effects/SkMatrixConvolutionImageFilter.cpp',
+ '<(skia_src_path)/effects/SkMergeImageFilter.cpp',
'<(skia_src_path)/effects/SkMorphologyImageFilter.cpp',
+ '<(skia_src_path)/effects/SkOffsetImageFilter.cpp',
'<(skia_src_path)/effects/SkPaintFlagsDrawFilter.cpp',
'<(skia_src_path)/effects/SkPixelXorXfermode.cpp',
'<(skia_src_path)/effects/SkPorterDuff.cpp',
@@ -86,6 +88,7 @@
'<(skia_include_path)/effects/SkLayerDrawLooper.h',
'<(skia_include_path)/effects/SkLayerRasterizer.h',
'<(skia_include_path)/effects/SkLightingImageFilter.h',
+ '<(skia_include_path)/effects/SkOffsetImageFilter.h',
'<(skia_include_path)/effects/SkMorphologyImageFilter.h',
'<(skia_include_path)/effects/SkPaintFlagsDrawFilter.h',
'<(skia_include_path)/effects/SkPixelXorXfermode.h',
diff --git a/gyp/everything.gyp b/gyp/everything.gyp
new file mode 100644
index 0000000000..e1d516dda6
--- /dev/null
+++ b/gyp/everything.gyp
@@ -0,0 +1,31 @@
+# Build EVERYTHING provided by Skia.
+# (Start with the "most" target, and then add targets that we intentionally
+# left out of "most". See most.gyp for an explanation of which targets are
+# left out of "most".)
+#
+# We used to call this the 'all' target, but in SOME cases that
+# conflicted with an automatically-generated 'all' target.
+# See https://code.google.com/p/skia/issues/detail?id=932
+#
+{
+ 'targets': [
+ {
+ 'target_name': 'everything',
+ 'type': 'none',
+ 'dependencies': ['most.gyp:most'],
+ 'conditions': [
+ ['skia_os in ("ios", "android", "nacl") or (skia_os == "mac" and skia_arch_width == 32)', {
+ # debugger is not supported on this platform
+ }, {
+ 'dependencies': [ 'debugger.gyp:debugger' ],
+ }],
+ ],
+ },
+ ],
+}
+
+# Local Variables:
+# tab-width:2
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=2 shiftwidth=2:
diff --git a/gyp/experimental.gyp b/gyp/experimental.gyp
index 09711ac400..09d5944b52 100644
--- a/gyp/experimental.gyp
+++ b/gyp/experimental.gyp
@@ -6,11 +6,13 @@
'include_dirs': [
'../include/config',
'../include/core',
+ '../experimental/AndroidPathRenderer',
],
'sources': [
'../experimental/SkSetPoly3To3.cpp',
'../experimental/SkSetPoly3To3_A.cpp',
'../experimental/SkSetPoly3To3_D.cpp',
+ '../experimental/AndroidPathRenderer/AndroidPathRenderer.cpp',
],
'direct_dependent_settings': {
'include_dirs': [
diff --git a/gyp/freetype.gyp b/gyp/freetype.gyp
index 4a885a1ccf..db617425a5 100644
--- a/gyp/freetype.gyp
+++ b/gyp/freetype.gyp
@@ -1,60 +1,82 @@
{
'targets': [
{
- 'target_name': 'skfreetype',
+ 'target_name': 'freetype',
'type': 'static_library',
'sources': [
- '../third_party/freetype/src/base/ftbbox.c',
- '../third_party/freetype/src/base/ftbitmap.c',
- '../third_party/freetype/src/base/ftglyph.c',
- '../third_party/freetype/src/base/ftlcdfil.c',
- '../third_party/freetype/src/base/ftstroke.c',
- '../third_party/freetype/src/base/ftxf86.c',
- '../third_party/freetype/src/base/ftbase.c',
- '../third_party/freetype/src/base/ftsystem.c',
- '../third_party/freetype/src/base/ftinit.c',
- '../third_party/freetype/src/base/ftgasp.c',
- '../third_party/freetype/src/base/ftfstype.c',
- '../third_party/freetype/src/raster/raster.c',
- '../third_party/freetype/src/sfnt/sfnt.c',
- '../third_party/freetype/src/smooth/smooth.c',
- '../third_party/freetype/src/autofit/autofit.c',
- '../third_party/freetype/src/truetype/truetype.c',
- '../third_party/freetype/src/cff/cff.c',
- '../third_party/freetype/src/psnames/psnames.c',
- '../third_party/freetype/src/pshinter/pshinter.c',
+ # base components (required)
+ '../third_party/externals/freetype/src/base/ftsystem.c',
+ '../third_party/externals/freetype/src/base/ftinit.c',
+ '../third_party/externals/freetype/src/base/ftdebug.c',
+ '../third_party/externals/freetype/src/base/ftbase.c',
-# added for linker
- '../third_party/freetype/src/lzw/ftlzw.c',
- '../third_party/freetype/src/gzip/ftgzip.c',
- '../third_party/freetype/src/cid/type1cid.c',
- '../third_party/freetype/src/bdf/bdf.c',
- '../third_party/freetype/src/psaux/psaux.c',
- '../third_party/freetype/src/pcf/pcf.c',
- '../third_party/freetype/src/pfr/pfr.c',
- '../third_party/freetype/src/type1/type1.c',
- '../third_party/freetype/src/type42/type42.c',
- '../third_party/freetype/src/winfonts/winfnt.c',
+ '../third_party/externals/freetype/src/base/ftbbox.c', # recommended, see <freetype/ftbbox.h>
+ '../third_party/externals/freetype/src/base/ftglyph.c', # recommended, see <freetype/ftglyph.h>
+
+ '../third_party/externals/freetype/src/base/ftbitmap.c', # optional, see <freetype/ftbitmap.h>
+ '../third_party/externals/freetype/src/base/ftfstype.c', # optional
+ '../third_party/externals/freetype/src/base/ftgasp.c', # optional, see <freetype/ftgasp.h>
+ '../third_party/externals/freetype/src/base/ftlcdfil.c', # optional, see <freetype/ftlcdfil.h>
+ '../third_party/externals/freetype/src/base/ftmm.c', # optional, see <freetype/ftmm.h>
+ '../third_party/externals/freetype/src/base/ftpatent.c', # optional
+ '../third_party/externals/freetype/src/base/ftstroke.c', # optional, see <freetype/ftstroke.h>
+ '../third_party/externals/freetype/src/base/ftsynth.c', # optional, see <freetype/ftsynth.h>
+ '../third_party/externals/freetype/src/base/fttype1.c', # optional, see <freetype/t1tables.h>
+ '../third_party/externals/freetype/src/base/ftwinfnt.c', # optional, see <freetype/ftwinfnt.h>
+ '../third_party/externals/freetype/src/base/ftxf86.c', # optional, see <freetype/ftxf86.h>
+
+ # font drivers (optional; at least one is needed)
+ '../third_party/externals/freetype/src/cff/cff.c', # CFF/OpenType font driver
+ '../third_party/externals/freetype/src/sfnt/sfnt.c', # SFNT files support (TrueType & OpenType)
+ '../third_party/externals/freetype/src/truetype/truetype.c', # TrueType font driver
+
+ # rasterizers (optional; at least one is needed for vector formats)
+ '../third_party/externals/freetype/src/raster/raster.c', # monochrome rasterizer
+ '../third_party/externals/freetype/src/smooth/smooth.c', # anti-aliasing rasterizer
+
+ # auxiliary modules (optional)
+ '../third_party/externals/freetype/src/autofit/autofit.c', # auto hinting module
+ '../third_party/externals/freetype/src/psaux/psaux.c', # PostScript Type 1 parsing
+ '../third_party/externals/freetype/src/pshinter/pshinter.c', # PS hinting module
+ '../third_party/externals/freetype/src/psnames/psnames.c', # PostScript glyph names support
],
'include_dirs': [
- '../third_party/freetype/internal',
- '../third_party/freetype/builds',
- '../third_party/freetype/include',
- '../third_party/freetype',
+ '../third_party/externals/freetype/internal',
+ '../third_party/externals/freetype/builds',
+ '../third_party/externals/freetype/include',
+ '../third_party/externals/freetype',
],
'cflags': [
- '-W',
- '-Wall',
- '-fPIC',
- '-DPIC',
- '-DDARWIN_NO_CARBON',
'-DFT2_BUILD_LIBRARY',
],
'direct_dependent_settings': {
'include_dirs': [
- '../third_party/freetype/include', # For ft2build.h
+ '../third_party/externals/freetype/include',
],
},
+ 'conditions': [
+ [ 'skia_os == "mac"', {
+ 'sources': [
+ '../third_party/externals/freetype/src/base/ftmac.c', # only on the Macintosh
+ ],
+ }],
+ [ 'skia_os == "android"', {
+ # These flags are used by the Android OS. They are probably overkill
+ # for Skia, but we add them for consistency.
+ 'cflags': [
+ '-W',
+ '-Wall',
+ '-fPIC',
+ '-DPIC',
+ '-DDARWIN_NO_CARBON',
+ '-DFT2_BUILD_LIBRARY',
+ '-O2',
+ ],
+ 'cflags!': [
+ '-fno-rtti', # supress warnings about invalid option of non-C++ code
+ ],
+ }],
+ ],
},
],
}
diff --git a/gyp/gm.gyp b/gyp/gm.gyp
index 6f0fc3d377..4f8637fd06 100644
--- a/gyp/gm.gyp
+++ b/gyp/gm.gyp
@@ -10,6 +10,7 @@
'include_dirs' : [
'../src/core',
'../src/pipe/utils/',
+ '../src/utils/',
],
'includes': [
'gmslides.gypi',
@@ -25,7 +26,9 @@
'skia_base_libs.gyp:skia_base_libs',
'effects.gyp:effects',
'images.gyp:images',
+ 'jsoncpp.gyp:jsoncpp',
'pdf.gyp:pdf',
+ 'utils.gyp:utils',
],
'conditions': [
['skia_os == "mac"', {
diff --git a/gyp/gmslides.gypi b/gyp/gmslides.gypi
index 46ec34d1f9..1553e744fe 100644
--- a/gyp/gmslides.gypi
+++ b/gyp/gmslides.gypi
@@ -12,6 +12,7 @@
'../gm/bitmapscroll.cpp',
'../gm/blend.cpp',
'../gm/blurs.cpp',
+ '../gm/blurrect.cpp',
'../gm/circles.cpp',
'../gm/colorfilterimagefilter.cpp',
'../gm/colormatrix.cpp',
@@ -29,6 +30,8 @@
'../gm/drawlooper.cpp',
'../gm/extractbitmap.cpp',
'../gm/emptypath.cpp',
+ '../gm/fatpathfill.cpp',
+ '../gm/factory.cpp',
'../gm/filltypes.cpp',
'../gm/filltypespersp.cpp',
'../gm/fontscaler.cpp',
@@ -48,15 +51,19 @@
'../gm/lcdtext.cpp',
'../gm/linepaths.cpp',
'../gm/matrixconvolution.cpp',
+ '../gm/modecolorfilters.cpp',
'../gm/morphology.cpp',
'../gm/ninepatchstretch.cpp',
'../gm/nocolorbleed.cpp',
'../gm/patheffects.cpp',
'../gm/pathfill.cpp',
+ '../gm/pathinterior.cpp',
'../gm/pathreverse.cpp',
'../gm/points.cpp',
'../gm/poly2poly.cpp',
'../gm/quadpaths.cpp',
+ '../gm/rrect.cpp',
+ '../gm/rrects.cpp',
'../gm/samplerstress.cpp',
'../gm/shaderbounds.cpp',
'../gm/shadertext.cpp',
@@ -64,7 +71,9 @@
'../gm/shadertext3.cpp',
'../gm/shadows.cpp',
'../gm/simpleaaclip.cpp',
+ '../gm/srcmode.cpp',
'../gm/strokefill.cpp',
+ '../gm/strokerect.cpp',
'../gm/strokerects.cpp',
'../gm/strokes.cpp',
'../gm/tablecolorfilter.cpp',
diff --git a/gyp/gpu.gyp b/gyp/gpu.gyp
index 0dc84eeaf8..d5223e40ef 100644
--- a/gyp/gpu.gyp
+++ b/gyp/gpu.gyp
@@ -21,6 +21,10 @@
'sources/': [ ['exclude', '_android.(h|cpp)$'],
],
}],
+ ['skia_os != "nacl"', {
+ 'sources/': [ ['exclude', '_nacl.(h|cpp)$'],
+ ],
+ }],
[ 'skia_os == "android"', {
'defines': [
'GR_ANDROID_BUILD=1',
@@ -71,6 +75,11 @@
],
},
}],
+ [ 'skia_texture_cache_mb_limit != 0', {
+ 'defines': [
+ 'GR_DEFAULT_TEXTURE_CACHE_MB_LIMIT=<(skia_texture_cache_mb_limit)',
+ ],
+ }],
],
'direct_dependent_settings': {
'conditions': [
@@ -204,10 +213,18 @@
'link_settings': {
'libraries': [
'-lGL',
+ '-lGLU',
'-lX11',
],
},
}],
+ [ 'skia_os == "nacl"', {
+ 'link_settings': {
+ 'libraries': [
+ '-lppapi_gles2',
+ ],
+ },
+ }],
[ 'skia_mesa and skia_os == "linux"', {
'link_settings': {
'libraries': [
diff --git a/gyp/gpu.gypi b/gyp/gpu.gypi
index c0a4a7be8f..7d3c0f2cbc 100644
--- a/gyp/gpu.gypi
+++ b/gyp/gpu.gypi
@@ -9,30 +9,30 @@
'variables': {
'gr_sources': [
'<(skia_include_path)/gpu/GrAARectRenderer.h',
+ '<(skia_include_path)/gpu/GrBackendEffectFactory.h',
'<(skia_include_path)/gpu/GrCacheID.h',
'<(skia_include_path)/gpu/GrClipData.h',
'<(skia_include_path)/gpu/GrColor.h',
'<(skia_include_path)/gpu/GrConfig.h',
'<(skia_include_path)/gpu/GrContext.h',
'<(skia_include_path)/gpu/GrContextFactory.h',
- '<(skia_include_path)/gpu/GrCustomStage.h',
- '<(skia_include_path)/gpu/GrCustomStageUnitTest.h',
+ '<(skia_include_path)/gpu/GrEffect.h',
+ '<(skia_include_path)/gpu/GrEffectStage.h',
+ '<(skia_include_path)/gpu/GrEffectUnitTest.h',
'<(skia_include_path)/gpu/GrFontScaler.h',
'<(skia_include_path)/gpu/GrGlyph.h',
'<(skia_include_path)/gpu/GrInstanceCounter.h',
'<(skia_include_path)/gpu/GrKey.h',
- '<(skia_include_path)/gpu/GrMatrix.h',
'<(skia_include_path)/gpu/GrNoncopyable.h',
'<(skia_include_path)/gpu/GrPaint.h',
+ '<(skia_include_path)/gpu/GrPathRendererChain.h',
'<(skia_include_path)/gpu/GrPoint.h',
- '<(skia_include_path)/gpu/GrProgramStageFactory.h',
'<(skia_include_path)/gpu/GrRect.h',
'<(skia_include_path)/gpu/GrRefCnt.h',
'<(skia_include_path)/gpu/GrRenderTarget.h',
'<(skia_include_path)/gpu/GrResource.h',
- '<(skia_include_path)/gpu/GrSamplerState.h',
- '<(skia_include_path)/gpu/GrScalar.h',
'<(skia_include_path)/gpu/GrSurface.h',
+ '<(skia_include_path)/gpu/GrTBackendEffectFactory.h',
'<(skia_include_path)/gpu/GrTextContext.h',
'<(skia_include_path)/gpu/GrTexture.h',
'<(skia_include_path)/gpu/GrTextureAccess.h',
@@ -60,13 +60,13 @@
'<(skia_src_path)/gpu/GrCacheID.cpp',
'<(skia_src_path)/gpu/GrClipData.cpp',
'<(skia_src_path)/gpu/GrContext.cpp',
- '<(skia_src_path)/gpu/GrCustomStage.cpp',
'<(skia_src_path)/gpu/GrDefaultPathRenderer.cpp',
'<(skia_src_path)/gpu/GrDefaultPathRenderer.h',
'<(skia_src_path)/gpu/GrDrawState.cpp',
'<(skia_src_path)/gpu/GrDrawState.h',
'<(skia_src_path)/gpu/GrDrawTarget.cpp',
'<(skia_src_path)/gpu/GrDrawTarget.h',
+ '<(skia_src_path)/gpu/GrEffect.cpp',
'<(skia_src_path)/gpu/GrGeometryBuffer.cpp',
'<(skia_src_path)/gpu/GrGeometryBuffer.h',
'<(skia_src_path)/gpu/GrClipMaskCache.h',
@@ -80,24 +80,23 @@
'<(skia_src_path)/gpu/GrIndexBuffer.h',
'<(skia_src_path)/gpu/GrInOrderDrawBuffer.cpp',
'<(skia_src_path)/gpu/GrInOrderDrawBuffer.h',
- '<(skia_src_path)/gpu/GrMatrix.cpp',
'<(skia_src_path)/gpu/GrMemory.cpp',
'<(skia_src_path)/gpu/GrMemoryPool.cpp',
'<(skia_src_path)/gpu/GrMemoryPool.h',
'<(skia_src_path)/gpu/GrPath.cpp',
'<(skia_src_path)/gpu/GrPath.h',
'<(skia_src_path)/gpu/GrPathRendererChain.cpp',
- '<(skia_src_path)/gpu/GrPathRendererChain.h',
'<(skia_src_path)/gpu/GrPathRenderer.cpp',
'<(skia_src_path)/gpu/GrPathRenderer.h',
'<(skia_src_path)/gpu/GrPathUtils.cpp',
'<(skia_src_path)/gpu/GrPathUtils.h',
'<(skia_src_path)/gpu/GrPlotMgr.h',
- '<(skia_src_path)/gpu/GrRandom.h',
'<(skia_src_path)/gpu/GrRectanizer.cpp',
'<(skia_src_path)/gpu/GrRectanizer.h',
'<(skia_src_path)/gpu/GrRedBlackTree.h',
'<(skia_src_path)/gpu/GrRenderTarget.cpp',
+ '<(skia_src_path)/gpu/GrReducedClip.cpp',
+ '<(skia_src_path)/gpu/GrReducedClip.h',
'<(skia_src_path)/gpu/GrResource.cpp',
'<(skia_src_path)/gpu/GrResourceCache.cpp',
'<(skia_src_path)/gpu/GrResourceCache.h',
@@ -128,8 +127,6 @@
'<(skia_src_path)/gpu/effects/Gr1DKernelEffect.h',
'<(skia_src_path)/gpu/effects/GrTextureStripAtlas.h',
'<(skia_src_path)/gpu/effects/GrTextureStripAtlas.cpp',
- '<(skia_src_path)/gpu/effects/GrColorTableEffect.cpp',
- '<(skia_src_path)/gpu/effects/GrColorTableEffect.h',
'<(skia_src_path)/gpu/effects/GrConfigConversionEffect.cpp',
'<(skia_src_path)/gpu/effects/GrConfigConversionEffect.h',
'<(skia_src_path)/gpu/effects/GrConvolutionEffect.cpp',
@@ -146,6 +143,10 @@
'<(skia_src_path)/gpu/gl/GrGLCreateNativeInterface_none.cpp',
'<(skia_src_path)/gpu/gl/GrGLDefaultInterface_none.cpp',
'<(skia_src_path)/gpu/gl/GrGLDefines.h',
+ '<(skia_src_path)/gpu/gl/GrGLEffect.cpp',
+ '<(skia_src_path)/gpu/gl/GrGLEffect.h',
+ '<(skia_src_path)/gpu/gl/GrGLEffectMatrix.cpp',
+ '<(skia_src_path)/gpu/gl/GrGLEffectMatrix.h',
'<(skia_src_path)/gpu/gl/GrGLIndexBuffer.cpp',
'<(skia_src_path)/gpu/gl/GrGLIndexBuffer.h',
'<(skia_src_path)/gpu/gl/GrGLInterface.cpp',
@@ -154,8 +155,6 @@
'<(skia_src_path)/gpu/gl/GrGLPath.h',
'<(skia_src_path)/gpu/gl/GrGLProgram.cpp',
'<(skia_src_path)/gpu/gl/GrGLProgram.h',
- '<(skia_src_path)/gpu/gl/GrGLProgramStage.cpp',
- '<(skia_src_path)/gpu/gl/GrGLProgramStage.h',
'<(skia_src_path)/gpu/gl/GrGLRenderTarget.cpp',
'<(skia_src_path)/gpu/gl/GrGLRenderTarget.h',
'<(skia_src_path)/gpu/gl/GrGLShaderBuilder.cpp',
@@ -230,11 +229,15 @@
'<(skia_src_path)/gpu/SkGrPixelRef.cpp',
'<(skia_src_path)/gpu/SkGrTexturePixelRef.cpp',
+ '<(skia_src_path)/image/SkImage_Gpu.cpp',
+ '<(skia_src_path)/image/SkSurface_Gpu.cpp',
+
'<(skia_src_path)/gpu/gl/SkGLContext.cpp'
],
'skgr_native_gl_sources': [
'<(skia_include_path)/gpu/gl/SkNativeGLContext.h',
'<(skia_src_path)/gpu/gl/mac/SkNativeGLContext_mac.cpp',
+ '<(skia_src_path)/gpu/gl/nacl/SkNativeGLContext_nacl.cpp',
'<(skia_src_path)/gpu/gl/win/SkNativeGLContext_win.cpp',
'<(skia_src_path)/gpu/gl/unix/SkNativeGLContext_unix.cpp',
'<(skia_src_path)/gpu/gl/android/SkNativeGLContext_android.cpp',
diff --git a/gyp/images.gyp b/gyp/images.gyp
index 01e5c703fd..05d0dc378f 100644
--- a/gyp/images.gyp
+++ b/gyp/images.gyp
@@ -18,7 +18,7 @@
'../include/images',
],
'sources': [
- '../include/images/SkFlipPixelRef.h',
+ '../include/images/SkBitmapFactory.h',
'../include/images/SkImageDecoder.h',
'../include/images/SkImageEncoder.h',
'../include/images/SkImageRef.h',
@@ -32,8 +32,8 @@
'../src/images/SkBitmapRegionDecoder.cpp',
'../src/images/SkBitmap_RLEPixels.h',
'../src/images/SkCreateRLEPixelRef.cpp',
+ '../src/images/SkBitmapFactory.cpp',
'../src/images/SkFDStream.cpp',
- '../src/images/SkFlipPixelRef.cpp',
'../src/images/SkImageDecoder.cpp',
'../src/images/SkImageDecoder_Factory.cpp',
'../src/images/SkImageDecoder_libbmp.cpp',
@@ -92,7 +92,7 @@
'../src/ports/SkImageDecoder_CG.cpp',
],
}],
- [ 'skia_os in ["linux", "freebsd", "openbsd", "solaris"]', {
+ [ 'skia_os in ["linux", "freebsd", "openbsd", "solaris", "nacl"]', {
'sources!': [
'../src/images/SkImageDecoder_libgif.cpp',
'../src/images/SkMovie_gif.cpp',
diff --git a/gyp/jsoncpp.gyp b/gyp/jsoncpp.gyp
new file mode 100644
index 0000000000..71815a79d7
--- /dev/null
+++ b/gyp/jsoncpp.gyp
@@ -0,0 +1,56 @@
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# TODO: This file was copied from the external dependency
+# third_party/externals/jsoncpp/jsoncpp.gyp , at revision 125399,
+# with directory paths modified to work at this level.
+#
+# It would be better for us to depend on that gypfile within the external
+# dependency, but so far we have been unable to make that work reliably.
+# See https://code.google.com/p/skia/issues/detail?id=1023
+
+{
+ 'targets': [
+ {
+ 'target_name': 'jsoncpp',
+ 'type': 'static_library',
+ 'defines': [
+ 'JSON_USE_EXCEPTION=0',
+ ],
+ 'sources': [
+ '../third_party/externals/jsoncpp/source/include/json/assertions.h',
+ '../third_party/externals/jsoncpp/source/include/json/autolink.h',
+ '../third_party/externals/jsoncpp/source/include/json/config.h',
+ '../third_party/externals/jsoncpp/source/include/json/features.h',
+ '../third_party/externals/jsoncpp/source/include/json/forwards.h',
+ '../third_party/externals/jsoncpp/source/include/json/json.h',
+ '../third_party/externals/jsoncpp/source/include/json/reader.h',
+ '../third_party/externals/jsoncpp/overrides/include/json/value.h',
+ '../third_party/externals/jsoncpp/source/include/json/writer.h',
+ '../third_party/externals/jsoncpp/source/src/lib_json/json_batchallocator.h',
+ '../third_party/externals/jsoncpp/source/src/lib_json/json_reader.cpp',
+ '../third_party/externals/jsoncpp/source/src/lib_json/json_tool.h',
+ '../third_party/externals/jsoncpp/overrides/src/lib_json/json_value.cpp',
+ '../third_party/externals/jsoncpp/source/src/lib_json/json_writer.cpp',
+ ],
+ 'include_dirs': [
+ '../third_party/externals/jsoncpp/overrides/include/',
+ '../third_party/externals/jsoncpp/source/include/',
+ '../third_party/externals/jsoncpp/source/src/lib_json/',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '../third_party/externals/jsoncpp/overrides/include/',
+ '../third_party/externals/jsoncpp/source/include/',
+ ],
+ },
+ },
+ ],
+}
+
+# Local Variables:
+# tab-width:2
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=2 shiftwidth=2:
diff --git a/gyp/most.gyp b/gyp/most.gyp
new file mode 100644
index 0000000000..319547e2fe
--- /dev/null
+++ b/gyp/most.gyp
@@ -0,0 +1,34 @@
+# Build ALMOST everything provided by Skia; this should be the default target.
+#
+# This omits the following targets that many developers won't want to build:
+# - debugger: this requires QT to build
+#
+{
+ 'targets': [
+ {
+ 'target_name': 'most',
+ 'type': 'none',
+ 'dependencies': [
+ # The minimal set of static libraries for basic Skia functionality.
+ 'skia_base_libs.gyp:skia_base_libs',
+
+ 'bench.gyp:bench',
+ 'gm.gyp:gm',
+ 'SampleApp.gyp:SampleApp',
+ 'tests.gyp:tests',
+ 'tools.gyp:tools',
+ ],
+ 'conditions': [
+ ['skia_os == "android"', {
+ 'dependencies': [ 'android_system.gyp:SkiaAndroidApp' ],
+ }],
+ ],
+ },
+ ],
+}
+
+# Local Variables:
+# tab-width:2
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=2 shiftwidth=2:
diff --git a/gyp/nacl.gyp b/gyp/nacl.gyp
new file mode 100644
index 0000000000..5edb4453e0
--- /dev/null
+++ b/gyp/nacl.gyp
@@ -0,0 +1,19 @@
+# Common entry point for all Skia executables running in NaCl
+{
+ 'targets': [
+ {
+ 'target_name': 'nacl_interface',
+ 'type': 'static_library',
+ 'dependencies': [
+ 'skia_base_libs.gyp:skia_base_libs',
+ ],
+ 'include_dirs': [
+ # For SkThreadUtils.h
+ '../src/utils',
+ ],
+ 'sources': [
+ '../../nacl/src/nacl_interface.cpp',
+ ],
+ },
+ ],
+}
diff --git a/gyp/opts.gyp b/gyp/opts.gyp
index b8d7e22960..de27b42588 100644
--- a/gyp/opts.gyp
+++ b/gyp/opts.gyp
@@ -32,7 +32,7 @@
'conditions': [
[ 'skia_arch_type == "x86" and skia_os != "ios"', {
'conditions': [
- [ 'skia_os in ["linux", "freebsd", "openbsd", "solaris"]', {
+ [ 'skia_os in ["linux", "freebsd", "openbsd", "solaris", "nacl"]', {
'cflags': [
'-msse2',
],
@@ -54,9 +54,12 @@
# ARM), the compiler doesn't like that.
'cflags!': [
'-fno-omit-frame-pointer',
+ '-mapcs-frame',
+ '-mapcs',
],
'cflags': [
'-fomit-frame-pointer',
+ '-mno-apcs-frame',
],
'variables': {
'arm_neon_optional%': '<(arm_neon_optional>',
@@ -108,7 +111,7 @@
'../src/core',
],
'conditions': [
- [ 'skia_os in ["linux", "freebsd", "openbsd", "solaris"]', {
+ [ 'skia_os in ["linux", "freebsd", "openbsd", "solaris", "nacl"]', {
'cflags': [
'-mssse3',
],
diff --git a/gyp/ports.gyp b/gyp/ports.gyp
index f743ad4f7f..a84f7883d7 100644
--- a/gyp/ports.gyp
+++ b/gyp/ports.gyp
@@ -20,6 +20,7 @@
'../src/utils',
],
'sources': [
+ '../src/ports/SkDebug_nacl.cpp',
'../src/ports/SkDebug_stdio.cpp',
'../src/ports/SkDebug_win.cpp',
'../src/ports/SkFontDescriptor.h',
@@ -39,36 +40,51 @@
],
'conditions': [
[ 'skia_os in ["linux", "freebsd", "openbsd", "solaris"]', {
- 'conditions': [
- [ 'skia_nacl', {
- 'defines': [
- 'SK_CAN_USE_DLOPEN=0',
- ],
- 'sources': [
- '../src/ports/SkFontHost_none.cpp',
- ],
- }, {
- 'defines': [
- #The font host requires at least FreeType 2.3.0 at runtime.
- 'SK_FONTHOST_FREETYPE_RUNTIME_VERSION=0x020300',
- 'SK_CAN_USE_DLOPEN=1',
- ],
- 'sources': [
- '../src/ports/SkFontHost_FreeType.cpp',
- '../src/ports/SkFontHost_FreeType_common.cpp',
- '../src/ports/SkFontHost_linux.cpp',
- ],
- 'link_settings': {
- 'libraries': [
- '-lfreetype',
- '-ldl',
- ],
- },
- }],
+ 'defines': [
+ #The font host requires at least FreeType 2.3.0 at runtime.
+ 'SK_FONTHOST_FREETYPE_RUNTIME_VERSION=0x020300',\
+ 'SK_CAN_USE_DLOPEN=1',
+ ],
+ 'link_settings': {
+ 'libraries': [
+ '-lfreetype',
+ '-ldl',
+ ],
+ },
+ 'sources': [
+ '../src/ports/SkFontHost_FreeType.cpp',
+ '../src/ports/SkFontHost_FreeType_common.cpp',
+ '../src/ports/SkFontHost_linux.cpp',
+ '../src/ports/SkThread_pthread.cpp',
+ ],
+ }],
+ [ 'skia_os == "nacl"', {
+ 'dependencies': [
+ # On other OS, we can dynamically link against freetype. For nacl,
+ # we have to include our own version since the naclports version is
+ # too old (<0x020300) to provide the functionality we need.
+ 'freetype.gyp:freetype',
+ ],
+ 'export_dependent_settings': [
+ 'freetype.gyp:freetype',
+ ],
+ 'defines': [
+ # We use Android's repo, which provides at least FreeType 2.4.0
+ 'SK_FONTHOST_FREETYPE_RUNTIME_VERSION=0x020400',\
],
'sources': [
+ '../src/ports/SkFontHost_FreeType.cpp',
+ '../src/ports/SkFontHost_FreeType_common.cpp',
+ '../src/ports/SkFontHost_linux.cpp',
'../src/ports/SkThread_pthread.cpp',
],
+ 'sources!': [
+ '../src/ports/SkDebug_stdio.cpp',
+ ],
+ }, {
+ 'sources!': [
+ '../src/ports/SkDebug_nacl.cpp',
+ ],
}],
[ 'skia_os == "mac"', {
'include_dirs': [
@@ -147,10 +163,10 @@
'../src/ports/SkFontHost_FreeType.cpp',
'../src/ports/SkFontHost_FreeType_common.cpp',
'../src/ports/FontHostConfiguration_android.cpp',
- #TODO: include the ports/SkImageRef_ashmem.cpp for non-NDK builds
+ '../src/ports/SkImageRef_ashmem.cpp',
],
'dependencies': [
- 'android_deps.gyp:ft2',
+ 'freetype.gyp:freetype',
'android_deps.gyp:expat',
],
}],
diff --git a/gyp/sfnt.gyp b/gyp/sfnt.gyp
index d4d9627ae8..66089e34ff 100644
--- a/gyp/sfnt.gyp
+++ b/gyp/sfnt.gyp
@@ -14,8 +14,13 @@
'sources': [
'../src/sfnt/SkIBMFamilyClass.h',
'../src/sfnt/SkOTTableTypes.h',
+ '../src/sfnt/SkOTTable_glyf.h',
'../src/sfnt/SkOTTable_head.h',
'../src/sfnt/SkOTTable_hhea.h',
+ '../src/sfnt/SkOTTable_loca.h',
+ '../src/sfnt/SkOTTable_maxp.h',
+ '../src/sfnt/SkOTTable_maxp_CFF.h',
+ '../src/sfnt/SkOTTable_maxp_TT.h',
'../src/sfnt/SkOTTable_name.h',
'../src/sfnt/SkOTTable_OS_2.h',
'../src/sfnt/SkOTTable_OS_2_V0.h',
diff --git a/gyp/shapeops_edge.gyp b/gyp/shapeops_edge.gyp
index b3f63f4653..854287650f 100644
--- a/gyp/shapeops_edge.gyp
+++ b/gyp/shapeops_edge.gyp
@@ -73,6 +73,7 @@
'../experimental/Intersection/QuarticRoot.cpp',
'../experimental/Intersection/QuarticRoot_Test.cpp',
'../experimental/Intersection/ShapeOps.cpp',
+ '../experimental/Intersection/ShapeOpRect4x4_Test.cpp',
'../experimental/Intersection/Simplify.cpp',
'../experimental/Intersection/SimplifyAddIntersectingTs_Test.cpp',
'../experimental/Intersection/SimplifyAngle_Test.cpp',
diff --git a/gyp/tests.gyp b/gyp/tests.gyp
index c91388c456..066435caf6 100644
--- a/gyp/tests.gyp
+++ b/gyp/tests.gyp
@@ -20,11 +20,15 @@
'../tests/AnnotationTest.cpp',
'../tests/AtomicTest.cpp',
'../tests/BitmapCopyTest.cpp',
+ '../tests/BitmapFactoryTest.cpp',
'../tests/BitmapGetColorTest.cpp',
+ '../tests/BitmapHeapTest.cpp',
+ '../tests/BitmapTransformerTest.cpp',
'../tests/BitSetTest.cpp',
'../tests/BlitRowTest.cpp',
'../tests/BlurTest.cpp',
'../tests/CanvasTest.cpp',
+ '../tests/ChecksumTest.cpp',
'../tests/ClampRangeTest.cpp',
'../tests/ClipCacheTest.cpp',
'../tests/ClipCubicTest.cpp',
@@ -53,6 +57,7 @@
'../tests/GrMemoryPoolTest.cpp',
'../tests/HashCacheTest.cpp',
'../tests/InfRectTest.cpp',
+ '../tests/LListTest.cpp',
'../tests/MathTest.cpp',
'../tests/MatrixTest.cpp',
'../tests/Matrix44Test.cpp',
@@ -77,6 +82,7 @@
'../tests/RefCntTest.cpp',
'../tests/RefDictTest.cpp',
'../tests/RegionTest.cpp',
+ '../tests/RoundRectTest.cpp',
'../tests/RTreeTest.cpp',
'../tests/ScalarTest.cpp',
'../tests/ShaderOpacityTest.cpp',
@@ -86,10 +92,11 @@
'../tests/SrcOverTest.cpp',
'../tests/StreamTest.cpp',
'../tests/StringTest.cpp',
- '../tests/TDLinkedListTest.cpp',
+ '../tests/StrokeTest.cpp',
'../tests/Test.cpp',
'../tests/Test.h',
'../tests/TestSize.cpp',
+ '../tests/TileGridTest.cpp',
'../tests/TLSTest.cpp',
'../tests/ToUnicode.cpp',
'../tests/UnicodeTest.cpp',
@@ -109,6 +116,7 @@
'images.gyp:images',
'pdf.gyp:pdf',
'tools.gyp:picture_utils',
+ 'utils.gyp:utils',
],
'conditions': [
[ 'skia_gpu == 1', {
diff --git a/gyp/tools.gyp b/gyp/tools.gyp
index f6e9a93b78..0cdb3197a0 100644
--- a/gyp/tools.gyp
+++ b/gyp/tools.gyp
@@ -28,7 +28,31 @@
'target_name': 'skdiff',
'type': 'executable',
'sources': [
+ '../tools/skdiff.cpp',
+ '../tools/skdiff.h',
+ '../tools/skdiff_html.cpp',
+ '../tools/skdiff_html.h',
'../tools/skdiff_main.cpp',
+ '../tools/skdiff_utils.cpp',
+ '../tools/skdiff_utils.h',
+ ],
+ 'dependencies': [
+ 'skia_base_libs.gyp:skia_base_libs',
+ 'effects.gyp:effects',
+ 'images.gyp:images',
+ ],
+ },
+ {
+ 'target_name': 'skimagediff',
+ 'type': 'executable',
+ 'sources': [
+ '../tools/skdiff.cpp',
+ '../tools/skdiff.h',
+ '../tools/skdiff_html.cpp',
+ '../tools/skdiff_html.h',
+ '../tools/skdiff_image.cpp',
+ '../tools/skdiff_utils.cpp',
+ '../tools/skdiff_utils.h',
],
'dependencies': [
'skia_base_libs.gyp:skia_base_libs',
@@ -101,11 +125,15 @@
'target_name': 'picture_renderer',
'type': 'static_library',
'sources': [
+ '../tools/PictureRenderer.h',
'../tools/PictureRenderer.cpp',
+ '../tools/CopyTilesRenderer.h',
+ '../tools/CopyTilesRenderer.cpp',
'../src/pipe/utils/SamplePipeControllers.h',
'../src/pipe/utils/SamplePipeControllers.cpp',
],
'include_dirs': [
+ '../src/core/',
'../src/pipe/utils/',
'../src/utils/',
],
@@ -120,6 +148,27 @@
],
},
{
+ 'target_name': 'render_pdfs',
+ 'type': 'executable',
+ 'sources': [
+ '../tools/render_pdfs_main.cpp',
+ '../tools/PdfRenderer.cpp',
+ '../tools/PdfRenderer.h',
+ ],
+ 'include_dirs': [
+ '../src/pipe/utils/',
+ '../src/utils/',
+ ],
+ 'dependencies': [
+ 'core.gyp:core',
+ 'effects.gyp:effects',
+ 'images.gyp:images',
+ 'pdf.gyp:pdf',
+ 'ports.gyp:ports',
+ 'tools.gyp:picture_utils',
+ ],
+ },
+ {
'target_name': 'picture_utils',
'type': 'static_library',
'sources': [
@@ -150,11 +199,14 @@
],
'sources': [
'../tools/filtermain.cpp',
+ '../tools/path_utils.cpp',
+ '../tools/path_utils.h',
],
'dependencies': [
'skia_base_libs.gyp:skia_base_libs',
'effects.gyp:effects',
'images.gyp:images',
+ 'tools.gyp:picture_utils',
],
},
],
diff --git a/gyp/utils.gyp b/gyp/utils.gyp
index cc25349045..4e3f6d45e5 100644
--- a/gyp/utils.gyp
+++ b/gyp/utils.gyp
@@ -5,6 +5,9 @@
'product_name': 'skia_utils',
'type': 'static_library',
'standalone_static_library': 1,
+ 'dependencies': [
+ 'cityhash',
+ ],
'include_dirs': [
'../include/config',
'../include/core',
@@ -18,6 +21,15 @@
'../src/utils',
],
'sources': [
+ # Classes for a threadpool.
+ '../include/utils/SkCondVar.h',
+ '../include/utils/SkCountdown.h',
+ '../include/utils/SkRunnable.h',
+ '../include/utils/SkThreadPool.h',
+ '../src/utils/SkCondVar.cpp',
+ '../src/utils/SkCountdown.cpp',
+ '../src/utils/SkThreadPool.cpp',
+
'../include/utils/SkBoundaryPatch.h',
'../include/utils/SkCamera.h',
'../include/utils/SkCubicInterval.h',
@@ -34,16 +46,23 @@
'../include/utils/SkParse.h',
'../include/utils/SkParsePaint.h',
'../include/utils/SkParsePath.h',
+ '../include/utils/SkPictureUtils.h',
'../include/utils/SkProxyCanvas.h',
'../include/utils/SkUnitMappers.h',
'../include/utils/SkWGL.h',
'../src/utils/SkBase64.cpp',
'../src/utils/SkBase64.h',
+ '../src/utils/SkBitmapChecksummer.cpp',
+ '../src/utils/SkBitmapChecksummer.h',
+ '../src/utils/SkBitmapTransformer.cpp',
+ '../src/utils/SkBitmapTransformer.h',
'../src/utils/SkBitSet.cpp',
'../src/utils/SkBitSet.h',
'../src/utils/SkBoundaryPatch.cpp',
'../src/utils/SkCamera.cpp',
+ '../src/utils/SkCityHash.cpp',
+ '../src/utils/SkCityHash.h',
'../src/utils/SkCubicInterval.cpp',
'../src/utils/SkCullPoints.cpp',
'../src/utils/SkDeferredCanvas.cpp',
@@ -60,6 +79,7 @@
'../src/utils/SkParse.cpp',
'../src/utils/SkParseColor.cpp',
'../src/utils/SkParsePath.cpp',
+ '../src/utils/SkPictureUtils.cpp',
'../src/utils/SkProxyCanvas.cpp',
'../src/utils/SkThreadUtils.h',
'../src/utils/SkThreadUtils_pthread.cpp',
@@ -120,12 +140,6 @@
],
}],
[ 'skia_os in ["linux", "freebsd", "openbsd", "solaris"]', {
- 'link_settings': {
- 'libraries': [
- '-lGL',
- '-lGLU',
- ],
- },
'sources!': [
'../src/utils/SkThreadUtils_pthread_other.cpp',
],
@@ -167,7 +181,7 @@
'../src/utils/win/SkIStream.cpp',
],
}],
- [ 'skia_nacl == 1', {
+ [ 'skia_os == "nacl"', {
'sources': [
'../src/utils/SkThreadUtils_pthread_other.cpp',
],
@@ -175,6 +189,11 @@
'../src/utils/SkThreadUtils_pthread_linux.cpp',
],
}],
+ [ 'skia_os == "android"', {
+ 'sources': [
+ '../src/utils/android/ashmem.c',
+ ],
+ }],
],
'direct_dependent_settings': {
'include_dirs': [
@@ -182,6 +201,25 @@
],
},
},
+ {
+ 'target_name': 'cityhash',
+ 'type': 'static_library',
+ 'standalone_static_library': 1,
+ 'include_dirs': [
+ '../include/config',
+ '../include/core',
+ '../src/utils/cityhash',
+ '../third_party/externals/cityhash/src',
+ ],
+ 'sources': [
+ '../third_party/externals/cityhash/src/city.cc',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '../third_party/externals/cityhash/src',
+ ],
+ },
+ },
],
}
diff --git a/gyp/views.gyp b/gyp/views.gyp
index 80be87e0d6..b0697a03d1 100644
--- a/gyp/views.gyp
+++ b/gyp/views.gyp
@@ -32,6 +32,7 @@
'../include/views/SkKey.h',
'../include/views/SkOSMenu.h',
'../include/views/SkOSWindow_Mac.h',
+ '../include/views/SkOSWindow_NaCl.h',
'../include/views/SkOSWindow_SDL.h',
'../include/views/SkOSWindow_Unix.h',
'../include/views/SkOSWindow_Win.h',
@@ -97,6 +98,13 @@
],
}],
[ 'skia_os in ["linux", "freebsd", "openbsd", "solaris"]', {
+ 'link_settings': {
+ 'libraries': [
+ '-lGL',
+ '-lGLU',
+ '-lX11',
+ ],
+ },
},{
'sources!': [
'../src/views/unix/SkOSWindow_Unix.cpp',
@@ -111,6 +119,17 @@
'../src/views/win/skia_win.cpp',
],
}],
+ [ 'skia_os == "nacl"', {
+ 'sources!': [
+ '../src/views/unix/SkOSWindow_Unix.cpp',
+ '../src/views/unix/keysym2ucs.c',
+ '../src/views/unix/skia_unix.cpp',
+ ],
+ }, {
+ 'sources!': [
+ '../src/views/nacl/SkOSWindow_NaCl.cpp',
+ ],
+ }],
[ 'skia_gpu == 1', {
'include_dirs': [
'../include/gpu',
diff --git a/gyp/xml.gyp b/gyp/xml.gyp
index b7fdb8397a..5cf06594b1 100644
--- a/gyp/xml.gyp
+++ b/gyp/xml.gyp
@@ -32,7 +32,7 @@
'../src/xml/SkXMLPullParser.cpp', #if 0 around class decl in header
],
'conditions': [
- [ 'skia_os in ["win", "mac", "linux", "freebsd", "openbsd", "solaris", "android", "ios"]', {
+ [ 'skia_os in ["win", "mac", "linux", "freebsd", "openbsd", "solaris", "android", "ios", "nacl"]', {
'sources!': [
# no jsapi.h by default on system
'../include/xml/SkJS.h',
diff --git a/gyp/zlib.gyp b/gyp/zlib.gyp
index 864a059bf3..fcd0ac3b40 100644
--- a/gyp/zlib.gyp
+++ b/gyp/zlib.gyp
@@ -29,7 +29,7 @@
},
'defines': [ 'SK_ZLIB_INCLUDE=<zlib.h>', ],
}],
- [ 'skia_os in ["linux", "freebsd", "openbsd", "solaris", "android"]', {
+ [ 'skia_os in ["linux", "freebsd", "openbsd", "solaris", "android", "nacl"]', {
'link_settings': { 'libraries': [ '-lz', ], },
'defines': [ 'SK_ZLIB_INCLUDE=<zlib.h>', ],
}],
diff --git a/gyp_skia b/gyp_skia
new file mode 100755
index 0000000000..803258971b
--- /dev/null
+++ b/gyp_skia
@@ -0,0 +1,127 @@
+#!/usr/bin/python
+
+# Copyright 2011 The Android Open Source Project
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# This script is a wrapper which invokes gyp with the correct --depth argument,
+# and supports the automatic regeneration of build files if all.gyp is
+# changed (Linux-only).
+
+import glob
+import os
+import platform
+import shlex
+import sys
+
+script_dir = os.path.dirname(__file__)
+
+# Directory within which we can find the gyp source.
+gyp_source_dir = os.path.join(script_dir, 'third_party', 'externals', 'gyp')
+
+# Directory within which we can find most of Skia's gyp configuration files.
+gyp_config_dir = os.path.join(script_dir, 'gyp')
+
+# Ensure we import our current gyp source's module, not any version
+# pre-installed in your PYTHONPATH.
+sys.path.insert(0, os.path.join(gyp_source_dir, 'pylib'))
+import gyp
+
+def additional_include_files(args=[]):
+ # Determine the include files specified on the command line.
+ # This doesn't cover all the different option formats you can use,
+ # but it's mainly intended to avoid duplicating flags on the automatic
+ # makefile regeneration which only uses this format.
+ specified_includes = set()
+ for arg in args:
+ if arg.startswith('-I') and len(arg) > 2:
+ specified_includes.add(os.path.realpath(arg[2:]))
+
+ result = []
+ def AddInclude(path):
+ if os.path.realpath(path) not in specified_includes:
+ result.append(path)
+
+ # Always include common.gypi.
+ # We do this, rather than including common.gypi explicitly in all our gyp
+ # files, so that gyp files we use but do not maintain (e.g.,
+ # third_party/externals/libjpeg/libjpeg.gyp) will include common.gypi too.
+ AddInclude(os.path.join(gyp_config_dir, 'common.gypi'))
+
+ return result
+
+# Return the directory where all generated files (including Makefiles) are to
+# be written.
+def get_output_dir():
+
+ # SKIA_OUT can be any directory either as a child of the standard out/
+ # directory or any given location on the file system (e.g. /tmp/skia)
+ output_dir = os.getenv('SKIA_OUT')
+
+ if not output_dir:
+ return os.path.join(os.path.abspath(script_dir), 'out')
+
+ if (os.name != 'posix' or
+ (sys.platform.startswith('darwin') and
+ (not os.getenv('GYP_GENERATORS') or
+ 'make' not in os.getenv('GYP_GENERATORS')))):
+ print 'ERROR: variable SKIA_OUT is not valid on Mac (using xcodebuild)' \
+ ' or Windows'
+ sys.exit(-1);
+
+ if os.path.isabs(output_dir):
+ return output_dir
+ else:
+ return os.path.join(os.path.abspath(script_dir), output_dir)
+
+
+if __name__ == '__main__':
+ args = sys.argv[1:]
+
+ # Set CWD to the directory containing this script.
+ # This allows us to launch it from other directories, in spite of gyp's
+ # finickyness about the current working directory.
+ # See http://b.corp.google.com/issue?id=5019517 ('Linux make build
+ # (from out dir) no longer runs skia_gyp correctly')
+ os.chdir(os.path.abspath(script_dir))
+
+ # This could give false positives since it doesn't actually do real option
+ # parsing. Oh well.
+ gyp_file_specified = False
+ for arg in args:
+ if arg.endswith('.gyp'):
+ gyp_file_specified = True
+ break
+
+ # If we didn't get a file, then fall back to assuming 'skia.gyp' from the
+ # same directory as the script.
+ # The gypfile must be passed as a relative path, not an absolute path,
+ # or else the gyp code doesn't write into the proper output dir.
+ if not gyp_file_specified:
+ args.append('skia.gyp')
+
+ args.extend(['-I' + i for i in additional_include_files(args)])
+ args.extend(['--depth', '.'])
+
+ # Tell gyp to write the Makefiles into output_dir
+ args.extend(['--generator-output', os.path.abspath(get_output_dir())])
+
+ # Tell make to write its output into the same dir
+ args.extend(['-Goutput_dir=.'])
+
+ # By default, we build 'most' instead of 'all' or 'everything'. See skia.gyp.
+ args.extend(['-Gdefault_target=most'])
+
+ # Special arguments for generating Visual Studio projects:
+ # - msvs_version forces generation of Visual Studio 2010 project so that we
+ # can use msbuild.exe
+ # - msvs_abspath_output is a workaround for
+ # http://code.google.com/p/gyp/issues/detail?id=201
+ args.extend(['-Gmsvs_version=2010'])
+
+ print 'Updating projects from gyp files...'
+ sys.stdout.flush()
+
+ # Off we go...
+ sys.exit(gyp.main(args))
diff --git a/include/core/SkBitmap.h b/include/core/SkBitmap.h
index 6ac9f6df80..6ff4be6b36 100644
--- a/include/core/SkBitmap.h
+++ b/include/core/SkBitmap.h
@@ -144,7 +144,7 @@ public:
/** Return the number of bytes from the pointer returned by getPixels()
to the end of the allocated space in the buffer. Required in
- cases where extractBitmap has been called.
+ cases where extractSubset has been called.
*/
size_t getSafeSize() const ;
@@ -223,6 +223,24 @@ public:
static size_t ComputeSize(Config, int width, int height);
/**
+ * This will brute-force return true if all of the pixels in the bitmap
+ * are opaque. If it fails to read the pixels, or encounters an error,
+ * it will return false.
+ *
+ * Since this can be an expensive operation, the bitmap stores a flag for
+ * this (isOpaque, setIsOpaque). Only call this if you need to compute this
+ * value from "unknown" pixels.
+ */
+ static bool ComputeIsOpaque(const SkBitmap&);
+
+ /**
+ * Calls ComputeIsOpaque, and passes its result to setIsOpaque().
+ */
+ void computeAndSetOpaquePredicate() {
+ this->setIsOpaque(ComputeIsOpaque(*this));
+ }
+
+ /**
* Return the bitmap's bounds [0, 0, width, height] as an SkRect
*/
void getBounds(SkRect* bounds) const;
diff --git a/include/core/SkCanvas.h b/include/core/SkCanvas.h
index 1f039921a3..3b3dbc4224 100644
--- a/include/core/SkCanvas.h
+++ b/include/core/SkCanvas.h
@@ -27,6 +27,7 @@ class SkDraw;
class SkDrawFilter;
class SkMetaData;
class SkPicture;
+class SkRRect;
class SkSurface_Base;
/** \class SkCanvas
@@ -85,12 +86,6 @@ public:
*/
SkDevice* getDevice() const;
- /** Specify a device for this canvas to draw into. If it is not null, its
- reference count is incremented. If the canvas was already holding a
- device, its reference count is decremented. The new device is returned.
- */
- virtual SkDevice* setDevice(SkDevice* device);
-
/**
* saveLayer() can create another device (which is later drawn onto
* the previous device). getTopDevice() returns the top-most device current
@@ -107,12 +102,6 @@ public:
SkDevice* getTopDevice(bool updateMatrixClip = false) const;
/**
- * Create a new raster device and make it current. This also returns
- * the new device.
- */
- SkDevice* setBitmapDevice(const SkBitmap& bitmap);
-
- /**
* Shortcut for getDevice()->createCompatibleDevice(...).
* If getDevice() == NULL, this method does nothing, and returns NULL.
*/
@@ -351,24 +340,47 @@ public:
*/
void resetMatrix();
- /** Modify the current clip with the specified rectangle.
- @param rect The rect to intersect with the current clip
- @param op The region op to apply to the current clip
- @return true if the canvas' clip is non-empty
- */
+ /**
+ * Modify the current clip with the specified rectangle.
+ * @param rect The rect to combine with the current clip
+ * @param op The region op to apply to the current clip
+ * @param doAntiAlias true if the clip should be antialiased
+ * @return true if the canvas' clip is non-empty
+ */
virtual bool clipRect(const SkRect& rect,
SkRegion::Op op = SkRegion::kIntersect_Op,
bool doAntiAlias = false);
- /** Modify the current clip with the specified path.
- @param path The path to apply to the current clip
- @param op The region op to apply to the current clip
- @return true if the canvas' new clip is non-empty
- */
+ /**
+ * Modify the current clip with the specified SkRRect.
+ * @param rrect The rrect to combine with the current clip
+ * @param op The region op to apply to the current clip
+ * @param doAntiAlias true if the clip should be antialiased
+ * @return true if the canvas' clip is non-empty
+ */
+ virtual bool clipRRect(const SkRRect& rrect,
+ SkRegion::Op op = SkRegion::kIntersect_Op,
+ bool doAntiAlias = false);
+
+ /**
+ * Modify the current clip with the specified path.
+ * @param path The path to combine with the current clip
+ * @param op The region op to apply to the current clip
+ * @param doAntiAlias true if the clip should be antialiased
+ * @return true if the canvas' new clip is non-empty
+ */
virtual bool clipPath(const SkPath& path,
SkRegion::Op op = SkRegion::kIntersect_Op,
bool doAntiAlias = false);
+ /** EXPERIMENTAL -- only used for testing
+ Set to false to force clips to be hard, even if doAntiAlias=true is
+ passed to clipRect or clipPath.
+ */
+ void setAllowSoftClip(bool allow) {
+ fAllowSoftClip = allow;
+ }
+
/** Modify the current clip with the specified region. Note that unlike
clipRect() and clipPath() which transform their arguments by the current
matrix, clipRegion() assumes its argument is already in device
@@ -581,7 +593,16 @@ public:
@param oval The rectangle bounds of the oval to be drawn
@param paint The paint used to draw the oval
*/
- void drawOval(const SkRect& oval, const SkPaint&);
+ virtual void drawOval(const SkRect& oval, const SkPaint&);
+
+ /**
+ * Draw the specified RRect using the specified paint The rrect will be filled or stroked
+ * based on the Style in the paint.
+ *
+ * @param rrect The round-rect to draw
+ * @param paint The paint used to draw the round-rect
+ */
+ virtual void drawRRect(const SkRRect& rrect, const SkPaint& paint);
/** Draw the specified circle using the specified paint. If radius is <= 0,
then nothing will be drawn. The circle will be filled
@@ -973,6 +994,17 @@ protected:
// can perform copy-on-write or invalidate any cached images
void predrawNotify();
+ /** DEPRECATED -- use constructor(device)
+
+ Marked as 'protected' to avoid new clients using this before we can
+ completely remove it.
+
+ Specify a device for this canvas to draw into. If it is not null, its
+ reference count is incremented. If the canvas was already holding a
+ device, its reference count is decremented. The new device is returned.
+ */
+ virtual SkDevice* setDevice(SkDevice* device);
+
private:
class MCRec;
@@ -1033,6 +1065,7 @@ private:
*/
mutable SkRectCompareType fLocalBoundsCompareType;
mutable bool fLocalBoundsCompareTypeDirty;
+ bool fAllowSoftClip;
const SkRectCompareType& getLocalClipBoundsCompareType() const {
if (fLocalBoundsCompareTypeDirty) {
@@ -1077,7 +1110,20 @@ public:
}
}
~SkAutoCanvasRestore() {
- fCanvas->restoreToCount(fSaveCount);
+ if (fCanvas) {
+ fCanvas->restoreToCount(fSaveCount);
+ }
+ }
+
+ /**
+ * Perform the restore now, instead of waiting for the destructor. Will
+ * only do this once.
+ */
+ void restore() {
+ if (fCanvas) {
+ fCanvas->restoreToCount(fSaveCount);
+ fCanvas = NULL;
+ }
}
private:
diff --git a/include/core/SkChecksum.h b/include/core/SkChecksum.h
index e5cc3d101f..9cb45c3307 100644
--- a/include/core/SkChecksum.h
+++ b/include/core/SkChecksum.h
@@ -30,6 +30,11 @@ public:
/**
* Compute a 32-bit checksum for a given data block
*
+ * WARNING: this algorithm is tuned for efficiency, not backward/forward
+ * compatibility. It may change at any time, so a checksum generated with
+ * one version of the Skia code may not match a checksum generated with
+ * a different version of the Skia code.
+ *
* @param data Memory address of the data block to be processed. Must be
* 32-bit aligned.
* @param size Size of the data block in bytes. Must be a multiple of 4.
@@ -83,4 +88,3 @@ public:
};
#endif
-
diff --git a/include/core/SkClipStack.h b/include/core/SkClipStack.h
index a67e0a5dd9..b39c5af6e1 100644
--- a/include/core/SkClipStack.h
+++ b/include/core/SkClipStack.h
@@ -9,11 +9,11 @@
#define SkClipStack_DEFINED
#include "SkDeque.h"
+#include "SkPath.h"
+#include "SkRect.h"
#include "SkRegion.h"
#include "SkTDArray.h"
-struct SkRect;
-class SkPath;
// Because a single save/restore state can have multiple clips, this class
// stores the stack depth (fSaveCount) and clips (fDeque) separately.
@@ -23,6 +23,239 @@ class SkPath;
// then the freshly decremented count.
class SK_API SkClipStack {
public:
+ enum BoundsType {
+ // The bounding box contains all the pixels that can be written to
+ kNormal_BoundsType,
+ // The bounding box contains all the pixels that cannot be written to.
+ // The real bound extends out to infinity and all the pixels outside
+ // of the bound can be written to. Note that some of the pixels inside
+ // the bound may also be writeable but all pixels that cannot be
+ // written to are guaranteed to be inside.
+ kInsideOut_BoundsType
+ };
+
+ class Element {
+ public:
+ enum Type {
+ //!< This element makes the clip empty (regardless of previous elements).
+ kEmpty_Type,
+ //!< This element combines a rect with the current clip using a set operation
+ kRect_Type,
+ //!< This element combines a path with the current clip using a set operation
+ kPath_Type,
+ };
+
+ Element() {
+ this->initCommon(0, SkRegion::kReplace_Op, false);
+ this->setEmpty();
+ }
+
+ Element(const SkRect& rect, SkRegion::Op op, bool doAA) {
+ this->initRect(0, rect, op, doAA);
+ }
+
+ Element(const SkPath& path, SkRegion::Op op, bool doAA) {
+ this->initPath(0, path, op, doAA);
+ }
+
+ bool operator== (const Element& element) const {
+ if (this == &element) {
+ return true;
+ }
+ if (fOp != element.fOp ||
+ fType != element.fType ||
+ fDoAA != element.fDoAA ||
+ fSaveCount != element.fSaveCount) {
+ return false;
+ }
+ switch (fType) {
+ case kPath_Type:
+ return fPath == element.fPath;
+ case kRect_Type:
+ return fRect == element.fRect;
+ case kEmpty_Type:
+ return true;
+ default:
+ SkDEBUGFAIL("Unexpected type.");
+ return false;
+ }
+ }
+ bool operator!= (const Element& element) const { return !(*this == element); }
+
+ //!< Call to get the type of the clip element.
+ Type getType() const { return fType; }
+
+ //!< Call if getType() is kPath to get the path.
+ const SkPath& getPath() const { return fPath; }
+
+ //!< Call if getType() is kRect to get the rect.
+ const SkRect& getRect() const { return fRect; }
+
+ //!< Call if getType() is not kEmpty to get the set operation used to combine this element.
+ SkRegion::Op getOp() const { return fOp; }
+
+ /** If getType() is not kEmpty this indicates whether the clip shape should be anti-aliased
+ when it is rasterized. */
+ bool isAA() const { return fDoAA; }
+
+ //!< Inverts the fill of the clip shape. Note that a kEmpty element remains kEmpty.
+ void invertShapeFillType();
+
+ //!< Sets the set operation represented by the element.
+ void setOp(SkRegion::Op op) { fOp = op; }
+
+ /** The GenID can be used by clip stack clients to cache representations of the clip. The
+ ID corresponds to the set of clip elements up to and including this element within the
+ stack not to the element itself. That is the same clip path in different stacks will
+ have a different ID since the elements produce different clip result in the context of
+ their stacks. */
+ int32_t getGenID() const { return fGenID; }
+
+ /**
+ * Gets the bounds of the clip element, either the rect or path bounds. (Whether the shape
+ * is inverse filled is not considered.)
+ */
+ const SkRect& getBounds() const {
+ static const SkRect kEmpty = { 0, 0, 0, 0 };
+ switch (fType) {
+ case kRect_Type:
+ return fRect;
+ case kPath_Type:
+ return fPath.getBounds();
+ case kEmpty_Type:
+ return kEmpty;
+ default:
+ SkDEBUGFAIL("Unexpected type.");
+ return kEmpty;
+ }
+ }
+
+ /**
+ * Conservatively checks whether the clip shape contains the rect param. (Whether the shape
+ * is inverse filled is not considered.)
+ */
+ bool contains(const SkRect& rect) const {
+ switch (fType) {
+ case kRect_Type:
+ return fRect.contains(rect);
+ case kPath_Type:
+ return fPath.conservativelyContainsRect(rect);
+ case kEmpty_Type:
+ return false;
+ default:
+ SkDEBUGFAIL("Unexpected type.");
+ return false;
+ }
+ }
+
+ /**
+ * Is the clip shape inverse filled.
+ */
+ bool isInverseFilled() const {
+ return kPath_Type == fType && fPath.isInverseFillType();
+ }
+
+ private:
+ friend class SkClipStack;
+
+ SkPath fPath;
+ SkRect fRect;
+ int fSaveCount; // save count of stack when this element was added.
+ SkRegion::Op fOp;
+ Type fType;
+ bool fDoAA;
+
+ /* fFiniteBoundType and fFiniteBound are used to incrementally update the clip stack's
+ bound. When fFiniteBoundType is kNormal_BoundsType, fFiniteBound represents the
+ conservative bounding box of the pixels that aren't clipped (i.e., any pixels that can be
+ drawn to are inside the bound). When fFiniteBoundType is kInsideOut_BoundsType (which
+ occurs when a clip is inverse filled), fFiniteBound represents the conservative bounding
+ box of the pixels that _are_ clipped (i.e., any pixels that cannot be drawn to are inside
+ the bound). When fFiniteBoundType is kInsideOut_BoundsType the actual bound is the
+ infinite plane. This behavior of fFiniteBoundType and fFiniteBound is required so that we
+ can capture the cancelling out of the extensions to infinity when two inverse filled
+ clips are Booleaned together. */
+ SkClipStack::BoundsType fFiniteBoundType;
+ SkRect fFiniteBound;
+
+ // When element is applied to the previous elements in the stack is the result known to be
+ // equivalent to a single rect intersection? IIOW, is the clip effectively a rectangle.
+ bool fIsIntersectionOfRects;
+
+ int fGenID;
+
+ Element(int saveCount) {
+ this->initCommon(saveCount, SkRegion::kReplace_Op, false);
+ this->setEmpty();
+ }
+
+ Element(int saveCount, const SkRect& rect, SkRegion::Op op, bool doAA) {
+ this->initRect(saveCount, rect, op, doAA);
+ }
+
+ Element(int saveCount, const SkPath& path, SkRegion::Op op, bool doAA) {
+ this->initPath(saveCount, path, op, doAA);
+ }
+
+ void initCommon(int saveCount, SkRegion::Op op, bool doAA) {
+ fSaveCount = saveCount;
+ fOp = op;
+ fDoAA = doAA;
+ // A default of inside-out and empty bounds means the bounds are effectively void as it
+ // indicates that nothing is known to be outside the clip.
+ fFiniteBoundType = kInsideOut_BoundsType;
+ fFiniteBound.setEmpty();
+ fIsIntersectionOfRects = false;
+ fGenID = kInvalidGenID;
+ }
+
+ void initRect(int saveCount, const SkRect& rect, SkRegion::Op op, bool doAA) {
+ fRect = rect;
+ fType = kRect_Type;
+ this->initCommon(saveCount, op, doAA);
+ }
+
+ void initPath(int saveCount, const SkPath& path, SkRegion::Op op, bool doAA) {
+ fPath = path;
+ fType = kPath_Type;
+ this->initCommon(saveCount, op, doAA);
+ }
+
+ void setEmpty() {
+ fType = kEmpty_Type;
+ fFiniteBound.setEmpty();
+ fFiniteBoundType = kNormal_BoundsType;
+ fIsIntersectionOfRects = false;
+ fRect.setEmpty();
+ fPath.reset();
+ fGenID = kEmptyGenID;
+ }
+
+ // All Element methods below are only used within SkClipStack.cpp
+ inline void checkEmpty() const;
+ inline bool canBeIntersectedInPlace(int saveCount, SkRegion::Op op) const;
+ /* This method checks to see if two rect clips can be safely merged into one. The issue here
+ is that to be strictly correct all the edges of the resulting rect must have the same
+ anti-aliasing. */
+ bool rectRectIntersectAllowed(const SkRect& newR, bool newAA) const;
+ /** Determines possible finite bounds for the Element given the previous element of the
+ stack */
+ void updateBoundAndGenID(const Element* prior);
+ // The different combination of fill & inverse fill when combining bounding boxes
+ enum FillCombo {
+ kPrev_Cur_FillCombo,
+ kPrev_InvCur_FillCombo,
+ kInvPrev_Cur_FillCombo,
+ kInvPrev_InvCur_FillCombo
+ };
+ // per-set operation functions used by updateBoundAndGenID().
+ inline void combineBoundsDiff(FillCombo combination, const SkRect& prevFinite);
+ inline void combineBoundsXOR(int combination, const SkRect& prevFinite);
+ inline void combineBoundsUnion(int combination, const SkRect& prevFinite);
+ inline void combineBoundsIntersection(int combination, const SkRect& prevFinite);
+ inline void combineBoundsRevDiff(int combination, const SkRect& prevFinite);
+ };
+
SkClipStack();
SkClipStack(const SkClipStack& b);
explicit SkClipStack(const SkRect& r);
@@ -39,17 +272,6 @@ public:
void save();
void restore();
- enum BoundsType {
- // The bounding box contains all the pixels that can be written to
- kNormal_BoundsType,
- // The bounding box contains all the pixels that cannot be written to.
- // The real bound extends out to infinity and all the pixels outside
- // of the bound can be written to. Note that some of the pixels inside
- // the bound may also be writeable but all pixels that cannot be
- // written to are guaranteed to be inside.
- kInsideOut_BoundsType
- };
-
/**
* getBounds places the current finite bound in its first parameter. In its
* second, it indicates which kind of bound is being returned. If
@@ -70,6 +292,13 @@ public:
*/
bool intersectRectWithClip(SkRect* devRect) const;
+ /**
+ * Returns true if the input rect in device space is entirely contained
+ * by the clip. A return value of false does not guarantee that the rect
+ * is not contained by the clip.
+ */
+ bool quickContains(const SkRect& devRect) const;
+
void clipDevRect(const SkIRect& ir, SkRegion::Op op) {
SkRect r;
r.set(ir);
@@ -103,7 +332,7 @@ public:
/**
* The generation ID has three reserved values to indicate special
- * (potentially ignoreable) cases
+ * (potentially ignorable) cases
*/
static const int32_t kInvalidGenID = 0;
static const int32_t kEmptyGenID = 1; // no pixels writeable
@@ -111,9 +340,6 @@ public:
int32_t getTopmostGenID() const;
-private:
- struct Rec;
-
public:
class Iter {
public:
@@ -129,36 +355,18 @@ public:
Iter(const SkClipStack& stack, IterStart startLoc);
- struct Clip {
- Clip() : fRect(NULL), fPath(NULL), fOp(SkRegion::kIntersect_Op),
- fDoAA(false) {}
- friend bool operator==(const Clip& a, const Clip& b);
- friend bool operator!=(const Clip& a, const Clip& b);
- const SkRect* fRect; // if non-null, this is a rect clip
- const SkPath* fPath; // if non-null, this is a path clip
- SkRegion::Op fOp;
- bool fDoAA;
- int32_t fGenID;
- };
-
/**
- * Return the clip for this element in the iterator. If next() returns
- * NULL, then the iterator is done. The type of clip is determined by
- * the pointers fRect and fPath:
- *
- * fRect==NULL fPath!=NULL path clip
- * fRect!=NULL fPath==NULL rect clip
- * fRect==NULL fPath==NULL empty clip
+ * Return the clip element for this iterator. If next()/prev() returns NULL, then the
+ * iterator is done.
*/
- const Clip* next();
- const Clip* prev();
+ const Element* next();
+ const Element* prev();
/**
- * Moves the iterator to the topmost clip with the specified RegionOp
- * and returns that clip. If no clip with that op is found,
- * returns NULL.
+ * Moves the iterator to the topmost element with the specified RegionOp and returns that
+ * element. If no clip element with that op is found, the first element is returned.
*/
- const Clip* skipToTopmost(SkRegion::Op op);
+ const Element* skipToTopmost(SkRegion::Op op);
/**
* Restarts the iterator on a clip stack.
@@ -167,14 +375,7 @@ public:
private:
const SkClipStack* fStack;
- Clip fClip;
SkDeque::Iter fIter;
-
- /**
- * updateClip updates fClip to the current state of fIter. It unifies
- * functionality needed by both next() and prev().
- */
- const Clip* updateClip(const SkClipStack::Rec* rec);
};
/**
@@ -193,7 +394,6 @@ public:
: INHERITED(stack, kBottom_IterStart) {
}
- using Iter::Clip;
using Iter::next;
/**
@@ -253,9 +453,9 @@ private:
mutable SkTDArray<ClipCallbackData> fCallbackData;
/**
- * Invoke all the purge callbacks passing in rec's generation ID.
+ * Invoke all the purge callbacks passing in element's generation ID.
*/
- void purgeClip(Rec* rec);
+ void purgeClip(Element* element);
/**
* Return the next unique generation ID.
diff --git a/include/core/SkColor.h b/include/core/SkColor.h
index e6d4352b69..f5055d01ee 100644
--- a/include/core/SkColor.h
+++ b/include/core/SkColor.h
@@ -73,18 +73,20 @@ static inline SkColor SkColorSetA(SkColor c, U8CPU a) {
// common colors
-#define SK_ColorBLACK 0xFF000000 //!< black SkColor value
-#define SK_ColorDKGRAY 0xFF444444 //!< dark gray SkColor value
-#define SK_ColorGRAY 0xFF888888 //!< gray SkColor value
-#define SK_ColorLTGRAY 0xFFCCCCCC //!< light gray SkColor value
-#define SK_ColorWHITE 0xFFFFFFFF //!< white SkColor value
-
-#define SK_ColorRED 0xFFFF0000 //!< red SkColor value
-#define SK_ColorGREEN 0xFF00FF00 //!< green SkColor value
-#define SK_ColorBLUE 0xFF0000FF //!< blue SkColor value
-#define SK_ColorYELLOW 0xFFFFFF00 //!< yellow SkColor value
-#define SK_ColorCYAN 0xFF00FFFF //!< cyan SkColor value
-#define SK_ColorMAGENTA 0xFFFF00FF //!< magenta SkColor value
+#define SK_ColorTRANSPARENT 0x00000000 //!< transparent SkColor value
+
+#define SK_ColorBLACK 0xFF000000 //!< black SkColor value
+#define SK_ColorDKGRAY 0xFF444444 //!< dark gray SkColor value
+#define SK_ColorGRAY 0xFF888888 //!< gray SkColor value
+#define SK_ColorLTGRAY 0xFFCCCCCC //!< light gray SkColor value
+#define SK_ColorWHITE 0xFFFFFFFF //!< white SkColor value
+
+#define SK_ColorRED 0xFFFF0000 //!< red SkColor value
+#define SK_ColorGREEN 0xFF00FF00 //!< green SkColor value
+#define SK_ColorBLUE 0xFF0000FF //!< blue SkColor value
+#define SK_ColorYELLOW 0xFFFFFF00 //!< yellow SkColor value
+#define SK_ColorCYAN 0xFF00FFFF //!< cyan SkColor value
+#define SK_ColorMAGENTA 0xFFFF00FF //!< magenta SkColor value
////////////////////////////////////////////////////////////////////////
diff --git a/include/core/SkColorFilter.h b/include/core/SkColorFilter.h
index 935b691160..4f67c8dbfe 100644
--- a/include/core/SkColorFilter.h
+++ b/include/core/SkColorFilter.h
@@ -15,6 +15,8 @@
#include "SkXfermode.h"
class SkBitmap;
+class GrEffect;
+class GrContext;
class SK_API SkColorFilter : public SkFlattenable {
public:
@@ -25,14 +27,14 @@ public:
* returns true, and sets (if not NULL) the color and mode appropriately.
* If not, this returns false and ignores the parameters.
*/
- virtual bool asColorMode(SkColor* color, SkXfermode::Mode* mode);
+ virtual bool asColorMode(SkColor* color, SkXfermode::Mode* mode) const;
/**
* If the filter can be represented by a 5x4 matrix, this
* returns true, and sets the matrix appropriately.
* If not, this returns false and ignores the parameter.
*/
- virtual bool asColorMatrix(SkScalar matrix[20]);
+ virtual bool asColorMatrix(SkScalar matrix[20]) const;
/**
* If the filter can be represented by per-component table, return true,
@@ -50,7 +52,7 @@ public:
* The original component value is the horizontal index for a given row,
* and the stored value at that index is the new value for that component.
*/
- virtual bool asComponentTable(SkBitmap* table);
+ virtual bool asComponentTable(SkBitmap* table) const;
/** Called with a scanline of colors, as if there was a shader installed.
The implementation writes out its filtered version into result[].
@@ -60,7 +62,7 @@ public:
@param result written by the filter
*/
virtual void filterSpan(const SkPMColor src[], int count,
- SkPMColor result[]) = 0;
+ SkPMColor result[]) const = 0;
/** Called with a scanline of colors, as if there was a shader installed.
The implementation writes out its filtered version into result[].
Note: shader and result may be the same buffer.
@@ -69,7 +71,7 @@ public:
@param result written by the filter
*/
virtual void filterSpan16(const uint16_t shader[], int count,
- uint16_t result[]);
+ uint16_t result[]) const;
enum Flags {
/** If set the filter methods will not change the alpha channel of the
@@ -85,7 +87,7 @@ public:
/** Returns the flags for this filter. Override in subclasses to return
custom flags.
*/
- virtual uint32_t getFlags() { return 0; }
+ virtual uint32_t getFlags() const { return 0; }
/**
* Apply this colorfilter to the specified SkColor. This routine handles
@@ -93,7 +95,7 @@ public:
* to SkColor. This method is not virtual, but will call filterSpan()
* which is virtual.
*/
- SkColor filterColor(SkColor);
+ SkColor filterColor(SkColor) const;
/** Create a colorfilter that uses the specified color and mode.
If the Mode is DST, this function will return NULL (since that
@@ -113,6 +115,11 @@ public:
*/
static SkColorFilter* CreateLightingFilter(SkColor mul, SkColor add);
+ /** A subclass may implement this factory function to work with the GPU backend. If the return
+ is non-NULL then the caller owns a ref on the returned object.
+ */
+ virtual GrEffect* asNewEffect(GrContext*) const;
+
SK_DECLARE_FLATTENABLE_REGISTRAR_GROUP()
protected:
SkColorFilter() {}
diff --git a/include/core/SkComposeShader.h b/include/core/SkComposeShader.h
index a8a8e0bb60..b0790bf393 100644
--- a/include/core/SkComposeShader.h
+++ b/include/core/SkComposeShader.h
@@ -34,11 +34,10 @@ public:
SkComposeShader(SkShader* sA, SkShader* sB, SkXfermode* mode = NULL);
virtual ~SkComposeShader();
- // override
- virtual bool setContext(const SkBitmap& device, const SkPaint& paint, const SkMatrix& matrix);
- virtual void shadeSpan(int x, int y, SkPMColor result[], int count);
- virtual void beginSession();
- virtual void endSession();
+ virtual bool setContext(const SkBitmap&, const SkPaint&,
+ const SkMatrix&) SK_OVERRIDE;
+ virtual void endContext() SK_OVERRIDE;
+ virtual void shadeSpan(int x, int y, SkPMColor[], int count) SK_OVERRIDE;
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkComposeShader)
diff --git a/include/core/SkData.h b/include/core/SkData.h
index 4db17a03b3..9a0cb09b60 100644
--- a/include/core/SkData.h
+++ b/include/core/SkData.h
@@ -128,7 +128,7 @@ private:
/**
* Specialized version of SkAutoTUnref<SkData> for automatically unref-ing a
- * SkData. If the SkData is null, data(), bytes() and size() will return 0.
+ * SkData.
*/
class SkAutoDataUnref : SkNoncopyable {
public:
diff --git a/include/core/SkDraw.h b/include/core/SkDraw.h
index 5db4bf0eb1..4ac7cf9fa0 100644
--- a/include/core/SkDraw.h
+++ b/include/core/SkDraw.h
@@ -76,7 +76,7 @@ public:
solely to assist in computing the mask's bounds (if the mode requests that).
*/
static bool DrawToMask(const SkPath& devPath, const SkIRect* clipBounds,
- SkMaskFilter* filter, const SkMatrix* filterMatrix,
+ const SkMaskFilter*, const SkMatrix* filterMatrix,
SkMask* mask, SkMask::CreateMode mode,
SkPaint::Style style);
diff --git a/include/core/SkDrawFilter.h b/include/core/SkDrawFilter.h
index 7471f9b2df..3944257851 100644
--- a/include/core/SkDrawFilter.h
+++ b/include/core/SkDrawFilter.h
@@ -32,14 +32,19 @@ public:
kBitmap_Type,
kRect_Type,
kPath_Type,
- kText_Type
+ kText_Type,
+ };
+
+ enum {
+ kTypeCount = kText_Type + 1
};
/**
* Called with the paint that will be used to draw the specified type.
- * The implementation may modify the paint as they wish.
+ * The implementation may modify the paint as they wish. If filter()
+ * returns false, the draw will be skipped.
*/
- virtual void filter(SkPaint*, Type) = 0;
+ virtual bool filter(SkPaint*, Type) = 0;
private:
typedef SkRefCnt INHERITED;
diff --git a/include/core/SkFloatingPoint.h b/include/core/SkFloatingPoint.h
index b3e490d604..d388cdb47b 100644
--- a/include/core/SkFloatingPoint.h
+++ b/include/core/SkFloatingPoint.h
@@ -87,4 +87,10 @@ static inline float sk_float_copysign(float x, float y) {
#define sk_float_ceil2int(x) (int)sk_float_ceil(x)
#endif
+extern const uint32_t gIEEENotANumber;
+extern const uint32_t gIEEEInfinity;
+
+#define SK_FloatNaN (*reinterpret_cast<const float*>(&gIEEENotANumber))
+#define SK_FloatInfinity (*reinterpret_cast<const float*>(&gIEEEInfinity))
+
#endif
diff --git a/include/core/SkImage.h b/include/core/SkImage.h
index 05350bfbfb..b8ebc3aca5 100644
--- a/include/core/SkImage.h
+++ b/include/core/SkImage.h
@@ -16,15 +16,13 @@ class SkCanvas;
class SkPaint;
class SkShader;
class GrContext;
-struct GrPlatformTextureDesc;
+class GrTexture;
// need for TileMode
#include "SkShader.h"
////// EXPERIMENTAL
-class SkColorSpace;
-
/**
* SkImage is an abstraction for drawing a rectagle of pixels, though the
* particular type of image could be actually storing its data on the GPU, or
@@ -63,13 +61,12 @@ public:
int fHeight;
ColorType fColorType;
AlphaType fAlphaType;
-
};
- static SkImage* NewRasterCopy(const Info&, SkColorSpace*, const void* pixels, size_t rowBytes);
- static SkImage* NewRasterData(const Info&, SkColorSpace*, SkData* pixels, size_t rowBytes);
+ static SkImage* NewRasterCopy(const Info&, const void* pixels, size_t rowBytes);
+ static SkImage* NewRasterData(const Info&, SkData* pixels, size_t rowBytes);
static SkImage* NewEncodedData(SkData*);
- static SkImage* NewTexture(GrContext*, const GrPlatformTextureDesc&);
+ static SkImage* NewTexture(GrTexture*);
int width() const { return fWidth; }
int height() const { return fHeight; }
diff --git a/include/core/SkImageFilter.h b/include/core/SkImageFilter.h
index 596da242ac..6831f2b994 100644
--- a/include/core/SkImageFilter.h
+++ b/include/core/SkImageFilter.h
@@ -17,7 +17,7 @@ class SkMatrix;
struct SkIPoint;
struct SkIRect;
struct SkRect;
-class GrCustomStage;
+class GrEffect;
class GrTexture;
/**
@@ -83,19 +83,22 @@ public:
/**
* Returns true if the filter can be expressed a single-pass
- * GrCustomStage, used to process this filter on the GPU, or false if
+ * GrEffect, used to process this filter on the GPU, or false if
* not.
*
- * If stage is non-NULL, a new GrCustomStage instance is stored
+ * If effect is non-NULL, a new GrEffect instance is stored
* in it. The caller assumes ownership of the stage, and it is up to the
* caller to unref it.
+ *
+ * The effect can assume its vertexCoords space maps 1-to-1 with texels
+ * in the texture.
*/
- virtual bool asNewCustomStage(GrCustomStage** stage, GrTexture*) const;
+ virtual bool asNewEffect(GrEffect** effect, GrTexture*) const;
/**
* Returns true if the filter can be processed on the GPU. This is most
* often used for multi-pass effects, where intermediate results must be
- * rendered to textures. For single-pass effects, use asNewCustomStage().
+ * rendered to textures. For single-pass effects, use asNewEffect().
* The default implementation returns false.
*/
virtual bool canFilterImageGPU() const;
@@ -133,10 +136,11 @@ public:
protected:
SkImageFilter(int inputCount, SkImageFilter** inputs);
- // The ... represents inputCount SkImageFilter pointers, upon which this
- // constructor will call SkSafeRef(). This is the same behaviour as
- // the SkImageFilter(int, SkImageFilter**) constructor above.
- explicit SkImageFilter(int inputCount, ...);
+ // Convenience constructor for 1-input filters.
+ explicit SkImageFilter(SkImageFilter* input);
+
+ // Convenience constructor for 2-input filters.
+ SkImageFilter(SkImageFilter* input1, SkImageFilter* input2);
virtual ~SkImageFilter();
diff --git a/include/core/SkMaskFilter.h b/include/core/SkMaskFilter.h
index 47cf516f2f..245de29b8e 100644
--- a/include/core/SkMaskFilter.h
+++ b/include/core/SkMaskFilter.h
@@ -40,7 +40,7 @@ public:
/** Returns the format of the resulting mask that this subclass will return
when its filterMask() method is called.
*/
- virtual SkMask::Format getFormat() = 0;
+ virtual SkMask::Format getFormat() const = 0;
/** Create a new mask by filter the src mask.
If src.fImage == null, then do not allocate or create the dst image
@@ -56,7 +56,7 @@ public:
@return true if the dst mask was correctly created.
*/
virtual bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&,
- SkIPoint* margin);
+ SkIPoint* margin) const;
enum BlurType {
kNone_BlurType, //!< this maskfilter is not a blur
@@ -91,12 +91,44 @@ public:
* The default impl calls filterMask with the src mask having no image,
* but subclasses may override this if they can compute the rect faster.
*/
- virtual void computeFastBounds(const SkRect& src, SkRect* dest);
+ virtual void computeFastBounds(const SkRect& src, SkRect* dest) const;
protected:
// empty for now, but lets get our subclass to remember to init us for the future
SkMaskFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {}
+ enum FilterReturn {
+ kFalse_FilterReturn,
+ kTrue_FilterReturn,
+ kUnimplemented_FilterReturn
+ };
+
+ struct NinePatch {
+ SkMask fMask; // fBounds must have [0,0] in its top-left
+ SkIRect fOuterRect; // width/height must be >= fMask.fBounds'
+ SkIPoint fCenter; // identifies center row/col for stretching
+ };
+
+ /**
+ * Override if your subclass can filter a rect, and return the answer as
+ * a ninepatch mask to be stretched over the returned outerRect. On success
+ * return kTrue_FilterReturn. On failure (e.g. out of memory) return
+ * kFalse_FilterReturn. If the normal filterMask() entry-point should be
+ * called (the default) return kUnimplemented_FilterReturn.
+ *
+ * By convention, the caller will take the center rol/col from the returned
+ * mask as the slice it can replicate horizontally and vertically as we
+ * stretch the mask to fit inside outerRect. It is an error for outerRect
+ * to be smaller than the mask's bounds. This would imply that the width
+ * and height of the mask should be odd. This is not required, just that
+ * the caller will call mask.fBounds.centerX() and centerY() to find the
+ * strips that will be replicated.
+ */
+ virtual FilterReturn filterRectsToNine(const SkRect[], int count,
+ const SkMatrix&,
+ const SkIRect& clipBounds,
+ NinePatch*) const;
+
private:
friend class SkDraw;
@@ -107,7 +139,7 @@ private:
*/
bool filterPath(const SkPath& devPath, const SkMatrix& devMatrix,
const SkRasterClip&, SkBounder*, SkBlitter* blitter,
- SkPaint::Style style);
+ SkPaint::Style style) const;
typedef SkFlattenable INHERITED;
};
diff --git a/include/core/SkMatrix.h b/include/core/SkMatrix.h
index f2c6512c5c..2d3786cf31 100644
--- a/include/core/SkMatrix.h
+++ b/include/core/SkMatrix.h
@@ -173,6 +173,8 @@ public:
/** Set the matrix to translate by (dx, dy).
*/
void setTranslate(SkScalar dx, SkScalar dy);
+ void setTranslate(const SkVector& v) { this->setTranslate(v.fX, v.fY); }
+
/** Set the matrix to scale by sx and sy, with a pivot point at (px, py).
The pivot point is the coordinate that should remain unchanged by the
specified transformation.
@@ -337,7 +339,16 @@ public:
set inverse to be the inverse of this matrix. If this matrix cannot be
inverted, ignore inverse and return false
*/
- bool SK_WARN_UNUSED_RESULT invert(SkMatrix* inverse) const;
+ bool SK_WARN_UNUSED_RESULT invert(SkMatrix* inverse) const {
+ // Allow the trivial case to be inlined.
+ if (this->isIdentity()) {
+ if (NULL != inverse) {
+ inverse->reset();
+ }
+ return true;
+ }
+ return this->invertNonIdentity(inverse);
+ }
/** Fills the passed array with affine identity values
in column major order.
@@ -618,6 +629,8 @@ private:
return ((fTypeMask & 0xF) == 0);
}
+ bool SK_WARN_UNUSED_RESULT invertNonIdentity(SkMatrix* inverse) const;
+
static bool Poly2Proc(const SkPoint[], SkMatrix*, const SkPoint& scale);
static bool Poly3Proc(const SkPoint[], SkMatrix*, const SkPoint& scale);
static bool Poly4Proc(const SkPoint[], SkMatrix*, const SkPoint& scale);
diff --git a/include/core/SkOSFile.h b/include/core/SkOSFile.h
index b5477cd5cf..79551ae206 100644
--- a/include/core/SkOSFile.h
+++ b/include/core/SkOSFile.h
@@ -7,7 +7,8 @@
*/
-//
+// TODO: add unittests for all these operations
+
#ifndef SkOSFile_DEFINED
#define SkOSFile_DEFINED
@@ -24,6 +25,12 @@ enum SkFILE_Flags {
kWrite_SkFILE_Flag = 0x02
};
+#ifdef _WIN32
+const static char SkPATH_SEPARATOR = '\\';
+#else
+const static char SkPATH_SEPARATOR = '/';
+#endif
+
SkFILE* sk_fopen(const char path[], SkFILE_Flags);
void sk_fclose(SkFILE*);
@@ -39,6 +46,17 @@ void sk_fflush(SkFILE*);
int sk_fseek( SkFILE*, size_t, int );
size_t sk_ftell( SkFILE* );
+// Returns true if something (file, directory, ???) exists at this path.
+bool sk_exists(const char *path);
+
+// Returns true if a directory exists at this path.
+bool sk_isdir(const char *path);
+
+// Create a new directory at this path; returns true if successful.
+// If the directory already existed, this will return true.
+// Description of the error, if any, will be written to stderr.
+bool sk_mkdir(const char* path);
+
class SkOSFile {
public:
class Iter {
@@ -79,4 +97,3 @@ private:
};
#endif
-
diff --git a/include/core/SkPath.h b/include/core/SkPath.h
index be5f612ef2..3b5424fe8f 100644
--- a/include/core/SkPath.h
+++ b/include/core/SkPath.h
@@ -28,6 +28,7 @@ class SkWriter32;
class SkAutoPathBoundsUpdate;
class SkString;
class SkPathRef;
+class SkRRect;
#ifndef SK_DEBUG_PATH_REF
#define SK_DEBUG_PATH_REF 0
@@ -88,7 +89,7 @@ public:
}
/** Returns true if the filltype is one of the Inverse variants */
- bool isInverseFillType() const { return (fFillType & 2) != 0; }
+ bool isInverseFillType() const { return IsInverseFillType((FillType)fFillType); }
/**
* Toggle between inverse and normal filltypes. This reverse the return
@@ -106,15 +107,15 @@ public:
};
/**
- * Return the path's convexity, as stored in the path. If it is currently
- * unknown, and the computeIfUnknown bool is true, then this will first
- * call ComputeConvexity() and then return that (cached) value.
+ * Return the path's convexity, as stored in the path. If it is currently unknown,
+ * then this function will attempt to compute the convexity (and cache the result).
*/
Convexity getConvexity() const {
- if (kUnknown_Convexity == fConvexity) {
- fConvexity = (uint8_t)ComputeConvexity(*this);
+ if (kUnknown_Convexity != fConvexity) {
+ return static_cast<Convexity>(fConvexity);
+ } else {
+ return this->internalGetConvexity();
}
- return (Convexity)fConvexity;
}
/**
@@ -127,8 +128,8 @@ public:
/**
* Store a convexity setting in the path. There is no automatic check to
- * see if this value actually agress with the return value from
- * ComputeConvexity().
+ * see if this value actually agrees with the return value that would be
+ * computed by getConvexity().
*
* Note: even if this is set to a "known" value, if the path is later
* changed (e.g. lineTo(), addRect(), etc.) then the cached value will be
@@ -137,20 +138,6 @@ public:
void setConvexity(Convexity);
/**
- * Compute the convexity of the specified path. This does not look at the
- * value stored in the path, but computes it directly from the path's data.
- *
- * This never returns kUnknown_Convexity.
- *
- * If there is more than one contour, this returns kConcave_Convexity.
- * If the contour is degenerate (e.g. there are fewer than 3 non-degenerate
- * segments), then this returns kConvex_Convexity.
- * The contour is treated as if it were closed, even if there is no kClose
- * verb.
- */
- static Convexity ComputeConvexity(const SkPath&);
-
- /**
* DEPRECATED: use getConvexity()
* Returns true if the path is flagged as being convex. This is not a
* confirmed by any analysis, it is just the value set earlier.
@@ -259,6 +246,16 @@ public:
*/
bool isRect(SkRect* rect) const;
+ /** Returns true if the path specifies a pair of nested rectangles. If so, and if
+ rect is not null, set rect[0] to the outer rectangle and rect[1] to the inner
+ rectangle. If the path does not specify a pair of nested rectangles, return
+ false and ignore rect.
+
+ @param rect If not null, returns the path as a pair of nested rectangles
+ @return true if the path describes a pair of nested rectangles
+ */
+ bool isNestedRects(SkRect rect[2]) const;
+
/** Return the number of points in the path
*/
int countPoints() const;
@@ -306,7 +303,7 @@ public:
}
/** Calling this will, if the internal cache of the bounds is out of date,
- update it so that subsequent calls to getBounds will be instanteous.
+ update it so that subsequent calls to getBounds will be instantaneous.
This also means that any copies or simple transformations of the path
will inherit the cached bounds.
*/
@@ -315,6 +312,14 @@ public:
this->getBounds();
}
+ /**
+ * Does a conservative test to see whether a rectangle is inside a path. Currently it only
+ * will ever return true for single convex contour paths. The empty-status of the rect is not
+ * considered (e.g. a rect that is a point can be inside a path). Points or line segments where
+ * the rect edge touches the path border are not considered containment violations.
+ */
+ bool conservativelyContainsRect(const SkRect& rect) const;
+
// Construction methods
/** Hint to the path to prepare for adding more points. This can allow the
@@ -498,68 +503,135 @@ public:
void close();
enum Direction {
+ /** Direction either has not been or could not be computed */
+ kUnknown_Direction,
/** clockwise direction for adding closed contours */
kCW_Direction,
/** counter-clockwise direction for adding closed contours */
- kCCW_Direction
+ kCCW_Direction,
};
/**
+ * Return the opposite of the specified direction. kUnknown is its own
+ * opposite.
+ */
+ static Direction OppositeDirection(Direction dir) {
+ static const Direction gOppositeDir[] = {
+ kUnknown_Direction, kCCW_Direction, kCW_Direction
+ };
+ return gOppositeDir[dir];
+ }
+
+ /**
+ * Returns whether or not a fill type is inverted
+ *
+ * kWinding_FillType -> false
+ * kEvenOdd_FillType -> false
+ * kInverseWinding_FillType -> true
+ * kInverseEvenOdd_FillType -> true
+ */
+ static bool IsInverseFillType(FillType fill) {
+ SK_COMPILE_ASSERT(0 == kWinding_FillType, fill_type_mismatch);
+ SK_COMPILE_ASSERT(1 == kEvenOdd_FillType, fill_type_mismatch);
+ SK_COMPILE_ASSERT(2 == kInverseWinding_FillType, fill_type_mismatch);
+ SK_COMPILE_ASSERT(3 == kInverseEvenOdd_FillType, fill_type_mismatch);
+ return (fill & 2) != 0;
+ }
+
+ /**
+ * Returns the equivalent non-inverted fill type to the given fill type
+ *
+ * kWinding_FillType -> kWinding_FillType
+ * kEvenOdd_FillType -> kEvenOdd_FillType
+ * kInverseWinding_FillType -> kWinding_FillType
+ * kInverseEvenOdd_FillType -> kEvenOdd_FillType
+ */
+ static FillType ConvertToNonInverseFillType(FillType fill) {
+ SK_COMPILE_ASSERT(0 == kWinding_FillType, fill_type_mismatch);
+ SK_COMPILE_ASSERT(1 == kEvenOdd_FillType, fill_type_mismatch);
+ SK_COMPILE_ASSERT(2 == kInverseWinding_FillType, fill_type_mismatch);
+ SK_COMPILE_ASSERT(3 == kInverseEvenOdd_FillType, fill_type_mismatch);
+ return (FillType)(fill & 1);
+ }
+
+ /**
* Tries to quickly compute the direction of the first non-degenerate
* contour. If it can be computed, return true and set dir to that
* direction. If it cannot be (quickly) determined, return false and ignore
- * the dir parameter.
+ * the dir parameter. If the direction was determined, it is cached to make
+ * subsequent calls return quickly.
*/
bool cheapComputeDirection(Direction* dir) const;
/**
* Returns true if the path's direction can be computed via
* cheapComputDirection() and if that computed direction matches the
- * specified direction.
+ * specified direction. If dir is kUnknown, returns true if the direction
+ * cannot be computed.
*/
bool cheapIsDirection(Direction dir) const {
- Direction computedDir;
- return this->cheapComputeDirection(&computedDir) && computedDir == dir;
+ Direction computedDir = kUnknown_Direction;
+ (void)this->cheapComputeDirection(&computedDir);
+ return computedDir == dir;
}
- /** Add a closed rectangle contour to the path
- @param rect The rectangle to add as a closed contour to the path
- @param dir The direction to wind the rectangle's contour
+ /** Returns true if the path specifies a rectangle. If so, and if isClosed is
+ not null, set isClosed to true if the path is closed. Also, if returning true
+ and direction is not null, return the rect direction. If the path does not
+ specify a rectangle, return false and ignore isClosed and direction.
+
+ @param isClosed If not null, set to true if the path is closed
+ @param direction If not null, set to the rectangle's direction
+ @return true if the path specifies a rectangle
*/
- void addRect(const SkRect& rect, Direction dir = kCW_Direction);
+ bool isRect(bool* isClosed, Direction* direction) const;
- /** Add a closed rectangle contour to the path
+ /**
+ * Add a closed rectangle contour to the path
+ * @param rect The rectangle to add as a closed contour to the path
+ * @param dir The direction to wind the rectangle's contour. Cannot be
+ * kUnknown_Direction.
+ */
+ void addRect(const SkRect& rect, Direction dir = kCW_Direction);
- @param left The left side of a rectangle to add as a closed contour
- to the path
- @param top The top of a rectangle to add as a closed contour to the
- path
- @param right The right side of a rectangle to add as a closed contour
- to the path
- @param bottom The bottom of a rectangle to add as a closed contour to
- the path
- @param dir The direction to wind the rectangle's contour
- */
+ /**
+ * Add a closed rectangle contour to the path
+ *
+ * @param left The left side of a rectangle to add as a closed contour
+ * to the path
+ * @param top The top of a rectangle to add as a closed contour to the
+ * path
+ * @param right The right side of a rectangle to add as a closed contour
+ * to the path
+ * @param bottom The bottom of a rectangle to add as a closed contour to
+ * the path
+ * @param dir The direction to wind the rectangle's contour. Cannot be
+ * kUnknown_Direction.
+ */
void addRect(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom,
Direction dir = kCW_Direction);
- /** Add a closed oval contour to the path
-
- @param oval The bounding oval to add as a closed contour to the path
- @param dir The direction to wind the oval's contour
- */
+ /**
+ * Add a closed oval contour to the path
+ *
+ * @param oval The bounding oval to add as a closed contour to the path
+ * @param dir The direction to wind the oval's contour. Cannot be
+ * kUnknown_Direction.
+ */
void addOval(const SkRect& oval, Direction dir = kCW_Direction);
- /** Add a closed circle contour to the path
-
- @param x The x-coordinate of the center of a circle to add as a
- closed contour to the path
- @param y The y-coordinate of the center of a circle to add as a
- closed contour to the path
- @param radius The radius of a circle to add as a closed contour to the
- path
- @param dir The direction to wind the circle's contour
- */
+ /**
+ * Add a closed circle contour to the path
+ *
+ * @param x The x-coordinate of the center of a circle to add as a
+ * closed contour to the path
+ * @param y The y-coordinate of the center of a circle to add as a
+ * closed contour to the path
+ * @param radius The radius of a circle to add as a closed contour to the
+ * path
+ * @param dir The direction to wind the circle's contour. Cannot be
+ * kUnknown_Direction.
+ */
void addCircle(SkScalar x, SkScalar y, SkScalar radius,
Direction dir = kCW_Direction);
@@ -571,26 +643,41 @@ public:
*/
void addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle);
- /** Add a closed round-rectangle contour to the path
- @param rect The bounds of a round-rectangle to add as a closed contour
- @param rx The x-radius of the rounded corners on the round-rectangle
- @param ry The y-radius of the rounded corners on the round-rectangle
- @param dir The direction to wind the round-rectangle's contour
- */
+ /**
+ * Add a closed round-rectangle contour to the path
+ * @param rect The bounds of a round-rectangle to add as a closed contour
+ * @param rx The x-radius of the rounded corners on the round-rectangle
+ * @param ry The y-radius of the rounded corners on the round-rectangle
+ * @param dir The direction to wind the rectangle's contour. Cannot be
+ * kUnknown_Direction.
+ */
void addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
Direction dir = kCW_Direction);
- /** Add a closed round-rectangle contour to the path. Each corner receives
- two radius values [X, Y]. The corners are ordered top-left, top-right,
- bottom-right, bottom-left.
- @param rect The bounds of a round-rectangle to add as a closed contour
- @param radii Array of 8 scalars, 4 [X,Y] pairs for each corner
- @param dir The direction to wind the round-rectangle's contour
- */
+ /**
+ * Add a closed round-rectangle contour to the path. Each corner receives
+ * two radius values [X, Y]. The corners are ordered top-left, top-right,
+ * bottom-right, bottom-left.
+ * @param rect The bounds of a round-rectangle to add as a closed contour
+ * @param radii Array of 8 scalars, 4 [X,Y] pairs for each corner
+ * @param dir The direction to wind the rectangle's contour. Cannot be
+ * kUnknown_Direction.
+ * Note: The radii here now go through the same constraint handling as the
+ * SkRRect radii (i.e., either radii at a corner being 0 implies a
+ * sqaure corner and oversized radii are proportionally scaled down).
+ */
void addRoundRect(const SkRect& rect, const SkScalar radii[],
Direction dir = kCW_Direction);
/**
+ * Add an SkRRect contour to the path
+ * @param rrect The rounded rect to add as a closed contour
+ * @param dir The winding direction for the new contour. Cannot be
+ * kUnknown_Direction.
+ */
+ void addRRect(const SkRRect& rrect, Direction dir = kCW_Direction);
+
+ /**
* Add a new contour made of just lines. This is just a fast version of
* the following:
* this->moveTo(pts[0]);
@@ -827,11 +914,12 @@ public:
private:
enum SerializationOffsets {
- kIsFinite_SerializationShift = 25,
- kIsOval_SerializationShift = 24,
- kConvexity_SerializationShift = 16,
- kFillType_SerializationShift = 8,
- kSegmentMask_SerializationShift = 0
+ kDirection_SerializationShift = 26, // requires 2 bits
+ kIsFinite_SerializationShift = 25, // requires 1 bit
+ kIsOval_SerializationShift = 24, // requires 1 bit
+ kConvexity_SerializationShift = 16, // requires 2 bits
+ kFillType_SerializationShift = 8, // requires 2 bits
+ kSegmentMask_SerializationShift = 0 // requires 3 bits
};
#if SK_DEBUG_PATH_REF
@@ -865,6 +953,7 @@ private:
uint8_t fSegmentMask;
mutable uint8_t fBoundsIsDirty;
mutable uint8_t fConvexity;
+ mutable uint8_t fDirection;
mutable SkBool8 fIsFinite; // only meaningful if bounds are valid
mutable SkBool8 fIsOval;
#ifdef SK_BUILD_FOR_ANDROID
@@ -900,8 +989,14 @@ private:
inline bool hasOnlyMoveTos() const;
+ Convexity internalGetConvexity() const;
+
+ bool isRectContour(bool allowPartial, int* currVerb, const SkPoint** pts,
+ bool* isClosed, Direction* direction) const;
+
friend class SkAutoPathBoundsUpdate;
friend class SkAutoDisableOvalCheck;
+ friend class SkAutoDisableDirectionCheck;
friend class SkBench_AddPathTest; // perf test pathTo/reversePathTo
};
diff --git a/include/core/SkPathEffect.h b/include/core/SkPathEffect.h
index 5d9b68af36..f9495cdc73 100644
--- a/include/core/SkPathEffect.h
+++ b/include/core/SkPathEffect.h
@@ -11,87 +11,14 @@
#define SkPathEffect_DEFINED
#include "SkFlattenable.h"
-#include "SkPaint.h"
+#include "SkPath.h"
+#include "SkPoint.h"
+#include "SkRect.h"
+#include "SkStrokeRec.h"
+#include "SkTDArray.h"
class SkPath;
-class SkStrokeRec {
-public:
- enum InitStyle {
- kHairline_InitStyle,
- kFill_InitStyle
- };
- SkStrokeRec(InitStyle style);
-
- SkStrokeRec(const SkStrokeRec&);
- explicit SkStrokeRec(const SkPaint&);
-
- enum Style {
- kHairline_Style,
- kFill_Style,
- kStroke_Style,
- kStrokeAndFill_Style
- };
-
- Style getStyle() const;
- SkScalar getWidth() const { return fWidth; }
- SkScalar getMiter() const { return fMiterLimit; }
- SkPaint::Cap getCap() const { return fCap; }
- SkPaint::Join getJoin() const { return fJoin; }
-
- bool isHairlineStyle() const {
- return kHairline_Style == this->getStyle();
- }
-
- bool isFillStyle() const {
- return kFill_Style == this->getStyle();
- }
-
- void setFillStyle();
- void setHairlineStyle();
- /**
- * Specify the strokewidth, and optionally if you want stroke + fill.
- * Note, if width==0, then this request is taken to mean:
- * strokeAndFill==true -> new style will be Fill
- * strokeAndFill==false -> new style will be Hairline
- */
- void setStrokeStyle(SkScalar width, bool strokeAndFill = false);
-
- void setStrokeParams(SkPaint::Cap cap, SkPaint::Join join, SkScalar miterLimit) {
- fCap = cap;
- fJoin = join;
- fMiterLimit = miterLimit;
- }
-
- /**
- * Returns true if this specifes any thick stroking, i.e. applyToPath()
- * will return true.
- */
- bool needToApply() const {
- Style style = this->getStyle();
- return (kStroke_Style == style) || (kStrokeAndFill_Style == style);
- }
-
- /**
- * Apply these stroke parameters to the src path, returning the result
- * in dst.
- *
- * If there was no change (i.e. style == hairline or fill) this returns
- * false and dst is unchanged. Otherwise returns true and the result is
- * stored in dst.
- *
- * src and dst may be the same path.
- */
- bool applyToPath(SkPath* dst, const SkPath& src) const;
-
-private:
- SkScalar fWidth;
- SkScalar fMiterLimit;
- SkPaint::Cap fCap;
- SkPaint::Join fJoin;
- bool fStrokeAndFill;
-};
-
/** \class SkPathEffect
SkPathEffect is the base class for objects in the SkPaint that affect
@@ -121,13 +48,62 @@ public:
* If this method returns true, the caller will apply (as needed) the
* resulting stroke-rec to dst and then draw.
*/
- virtual bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*) = 0;
+ virtual bool filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec*) const = 0;
/**
* Compute a conservative bounds for its effect, given the src bounds.
* The baseline implementation just assigns src to dst.
*/
- virtual void computeFastBounds(SkRect* dst, const SkRect& src);
+ virtual void computeFastBounds(SkRect* dst, const SkRect& src) const;
+
+ /** \class PointData
+
+ PointData aggregates all the information needed to draw the point
+ primitives returned by an 'asPoints' call.
+ */
+ class PointData {
+ public:
+ PointData()
+ : fFlags(0)
+ , fPoints(NULL)
+ , fNumPoints(0) {
+ fSize.set(SK_Scalar1, SK_Scalar1);
+ // 'asPoints' needs to initialize/fill-in 'fClipRect' if it sets
+ // the kUseClip flag
+ };
+ ~PointData() {
+ delete [] fPoints;
+ }
+
+ // TODO: consider using passed-in flags to limit the work asPoints does.
+ // For example, a kNoPath flag could indicate don't bother generating
+ // stamped solutions.
+
+ // Currently none of these flags are supported.
+ enum PointFlags {
+ kCircles_PointFlag = 0x01, // draw points as circles (instead of rects)
+ kUsePath_PointFlag = 0x02, // draw points as stamps of the returned path
+ kUseClip_PointFlag = 0x04, // apply 'fClipRect' before drawing the points
+ };
+
+ uint32_t fFlags; // flags that impact the drawing of the points
+ SkPoint* fPoints; // the center point of each generated point
+ int fNumPoints; // number of points in fPoints
+ SkVector fSize; // the size to draw the points
+ SkRect fClipRect; // clip required to draw the points (if kUseClip is set)
+ SkPath fPath; // 'stamp' to be used at each point (if kUsePath is set)
+
+ SkPath fFirst; // If not empty, contains geometry for first point
+ SkPath fLast; // If not empty, contains geometry for last point
+ };
+
+ /**
+ * Does applying this path effect to 'src' yield a set of points? If so,
+ * optionally return the points in 'results'.
+ */
+ virtual bool asPoints(PointData* results, const SkPath& src,
+ const SkStrokeRec&, const SkMatrix&) const;
protected:
SkPathEffect(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {}
@@ -177,7 +153,8 @@ public:
SkComposePathEffect(SkPathEffect* outer, SkPathEffect* inner)
: INHERITED(outer, inner) {}
- virtual bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*) SK_OVERRIDE;
+ virtual bool filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec*) const SK_OVERRIDE;
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkComposePathEffect)
@@ -207,7 +184,8 @@ public:
SkSumPathEffect(SkPathEffect* first, SkPathEffect* second)
: INHERITED(first, second) {}
- virtual bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*) SK_OVERRIDE;
+ virtual bool filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec*) const SK_OVERRIDE;
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkSumPathEffect)
diff --git a/include/core/SkPicture.h b/include/core/SkPicture.h
index 203efde916..3525695d02 100644
--- a/include/core/SkPicture.h
+++ b/include/core/SkPicture.h
@@ -13,6 +13,7 @@
#include "SkRefCnt.h"
#include "SkSerializationHelpers.h"
+class SkBBoxHierarchy;
class SkBitmap;
class SkCanvas;
class SkPicturePlayback;
@@ -147,17 +148,31 @@ public:
*/
void abortPlayback();
-private:
- int fWidth, fHeight;
- SkPictureRecord* fRecord;
+protected:
+ // V2 : adds SkPixelRef's generation ID.
+ // V3 : PictInfo tag at beginning, and EOF tag at the end
+ // V4 : move SkPictInfo to be the header
+ // V5 : don't read/write FunctionPtr on cross-process (we can detect that)
+ // V6 : added serialization of SkPath's bounds (and packed its flags tighter)
+ // V7 : changed drawBitmapRect(IRect) to drawBitmapRectToRect(Rect)
+ // V8 : Add an option for encoding bitmaps
+ // V9 : Allow the reader and writer of an SKP disagree on whether to support
+ // SK_SUPPORT_HINTING_SCALE_FACTOR
+ // V10: add drawRRect, drawOval, clipRRect
+ static const uint32_t PICTURE_VERSION = 10;
+
+ // fPlayback, fRecord, fWidth & fHeight are protected to allow derived classes to
+ // install their own SkPicturePlayback-derived players,SkPictureRecord-derived
+ // recorders and set the picture size
SkPicturePlayback* fPlayback;
+ SkPictureRecord* fRecord;
+ int fWidth, fHeight;
- /** Used by the R-Tree when kOptimizeForClippedPlayback_RecordingFlag is
- set, these were empirically determined to produce reasonable performance
- in most cases.
- */
- static const int kRTreeMinChildren = 6;
- static const int kRTreeMaxChildren = 11;
+ // For testing. Derived classes may instantiate an alternate
+ // SkBBoxHierarchy implementation
+ virtual SkBBoxHierarchy* createBBoxHierarchy() const;
+
+private:
friend class SkFlatPicture;
friend class SkPicturePlayback;
diff --git a/include/core/SkPixelRef.h b/include/core/SkPixelRef.h
index 9222b29e43..618cc711c9 100644
--- a/include/core/SkPixelRef.h
+++ b/include/core/SkPixelRef.h
@@ -15,7 +15,22 @@
#include "SkString.h"
#include "SkFlattenable.h"
+#ifdef SK_DEBUG
+ /**
+ * Defining SK_IGNORE_PIXELREF_SETPRELOCKED will force all pixelref
+ * subclasses to correctly handle lock/unlock pixels. For performance
+ * reasons, simple malloc-based subclasses call setPreLocked() to skip
+ * the overhead of implementing these calls.
+ *
+ * This build-flag disables that optimization, to add in debugging our
+ * call-sites, to ensure that they correctly balance their calls of
+ * lock and unlock.
+ */
+// #define SK_IGNORE_PIXELREF_SETPRELOCKED
+#endif
+
class SkColorTable;
+class SkData;
struct SkIRect;
class SkMutex;
@@ -50,6 +65,8 @@ public:
*/
bool isLocked() const { return fLockCount > 0; }
+ SkDEBUGCODE(int getLockCount() const { return fLockCount; })
+
/** Call to access the pixel memory, which is returned. Balance with a call
to unlockPixels().
*/
@@ -113,17 +130,36 @@ public:
*/
void setURI(const SkString& uri) { fURI = uri; }
+ /**
+ * If the pixelRef has an encoded (i.e. compressed) representation,
+ * return a ref to its data. If the pixelRef
+ * is uncompressed or otherwise does not have this form, return NULL.
+ *
+ * If non-null is returned, the caller is responsible for calling unref()
+ * on the data when it is finished.
+ */
+ SkData* refEncodedData() {
+ return this->onRefEncodedData();
+ }
+
/** Are we really wrapping a texture instead of a bitmap?
*/
virtual SkGpuTexture* getTexture() { return NULL; }
bool readPixels(SkBitmap* dst, const SkIRect* subset = NULL);
- /** Makes a deep copy of this PixelRef, respecting the requested config.
- Returns NULL if either there is an error (e.g. the destination could
- not be created with the given config), or this PixelRef does not
- support deep copies. */
- virtual SkPixelRef* deepCopy(SkBitmap::Config config) { return NULL; }
+ /**
+ * Makes a deep copy of this PixelRef, respecting the requested config.
+ * @param config Desired config.
+ * @param subset Subset of this PixelRef to copy. Must be fully contained within the bounds of
+ * of this PixelRef.
+ * @return A new SkPixelRef, or NULL if either there is an error (e.g. the destination could
+ * not be created with the given config), or this PixelRef does not support deep
+ * copies.
+ */
+ virtual SkPixelRef* deepCopy(SkBitmap::Config config, const SkIRect* subset = NULL) {
+ return NULL;
+ }
#ifdef SK_BUILD_FOR_ANDROID
/**
@@ -163,6 +199,9 @@ protected:
*/
virtual bool onReadPixels(SkBitmap* dst, const SkIRect* subsetOrNull);
+ // default impl returns NULL.
+ virtual SkData* onRefEncodedData();
+
/** Return the mutex associated with this pixelref. This value is assigned
in the constructor, and cannot change during the lifetime of the object.
*/
diff --git a/include/core/SkPostConfig.h b/include/core/SkPostConfig.h
index cbfe9b126a..12fe87dafe 100644
--- a/include/core/SkPostConfig.h
+++ b/include/core/SkPostConfig.h
@@ -30,6 +30,14 @@
#define SK_SCALAR_IS_FLOAT
#endif
+#if defined(SK_MSCALAR_IS_DOUBLE) && defined(SK_MSCALAR_IS_FLOAT)
+ #error "cannot define both SK_MSCALAR_IS_DOUBLE and SK_MSCALAR_IS_FLOAT"
+#elif !defined(SK_MSCALAR_IS_DOUBLE) && !defined(SK_MSCALAR_IS_FLOAT)
+ // default is double, as that is faster given our impl uses doubles
+ // for intermediate calculations.
+ #define SK_MSCALAR_IS_DOUBLE
+#endif
+
#if defined(SK_CPU_LENDIAN) && defined(SK_CPU_BENDIAN)
#error "cannot define both SK_CPU_LENDIAN and SK_CPU_BENDIAN"
#elif !defined(SK_CPU_LENDIAN) && !defined(SK_CPU_BENDIAN)
diff --git a/include/core/SkPreConfig.h b/include/core/SkPreConfig.h
index 756aaea6e0..393a348ea8 100644
--- a/include/core/SkPreConfig.h
+++ b/include/core/SkPreConfig.h
@@ -16,7 +16,7 @@
//////////////////////////////////////////////////////////////////////
-#if !defined(SK_BUILD_FOR_ANDROID) && !defined(SK_BUILD_FOR_ANDROID_NDK) && !defined(SK_BUILD_FOR_IOS) && !defined(SK_BUILD_FOR_PALM) && !defined(SK_BUILD_FOR_WINCE) && !defined(SK_BUILD_FOR_WIN32) && !defined(SK_BUILD_FOR_UNIX) && !defined(SK_BUILD_FOR_MAC) && !defined(SK_BUILD_FOR_SDL) && !defined(SK_BUILD_FOR_BREW)
+#if !defined(SK_BUILD_FOR_ANDROID) && !defined(SK_BUILD_FOR_ANDROID_NDK) && !defined(SK_BUILD_FOR_IOS) && !defined(SK_BUILD_FOR_PALM) && !defined(SK_BUILD_FOR_WINCE) && !defined(SK_BUILD_FOR_WIN32) && !defined(SK_BUILD_FOR_UNIX) && !defined(SK_BUILD_FOR_MAC) && !defined(SK_BUILD_FOR_SDL) && !defined(SK_BUILD_FOR_BREW) && !defined(SK_BUILD_FOR_NACL)
#ifdef __APPLE__
#include "TargetConditionals.h"
diff --git a/include/core/SkRRect.h b/include/core/SkRRect.h
new file mode 100644
index 0000000000..b09d27ac20
--- /dev/null
+++ b/include/core/SkRRect.h
@@ -0,0 +1,270 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkRRect_DEFINED
+#define SkRRect_DEFINED
+
+#include "SkRect.h"
+#include "SkPoint.h"
+
+class SkPath;
+
+// Path forward:
+// core work
+// add validate method (all radii positive, all radii sums < rect size, etc.)
+// add contains(SkRect&) - for clip stack
+// add contains(SkRRect&) - for clip stack
+// add heart rect computation (max rect inside RR)
+// add 9patch rect computation
+// add growToInclude(SkPath&)
+// analysis
+// use growToInclude to fit skp round rects & generate stats (RRs vs. real paths)
+// check on # of rectorus's the RRs could handle
+// rendering work
+// add entry points (clipRRect, drawRRect) - plumb down to SkDevice
+// update SkPath.addRRect() to take an SkRRect - only use quads
+// -- alternatively add addRRectToPath here
+// add GM and bench
+// clipping opt
+// update SkClipStack to perform logic with RRs
+// further out
+// add RR rendering shader to Ganesh (akin to cicle drawing code)
+// - only for simple RRs
+// detect and triangulate RRectorii rather than falling back to SW in Ganesh
+//
+
+/** \class SkRRect
+
+ The SkRRect class represents a rounded rect with a potentially different
+ radii for each corner. It does not have a constructor so must be
+ initialized with one of the initialization functions (e.g., setEmpty,
+ setRectRadii, etc.)
+
+ This class is intended to roughly match CSS' border-*-*-radius capabilities.
+ This means:
+ If either of a corner's radii are 0 the corner will be square.
+ Negative radii are not allowed (they are clamped to zero).
+ If the corner curves overlap they will be proportionally reduced to fit.
+*/
+class SK_API SkRRect {
+public:
+ /**
+ * Enum to capture the various possible subtypes of RR. Accessed
+ * by type(). The subtypes become progressively less restrictive.
+ */
+ enum Type {
+ // !< Internal indicator that the sub type must be computed.
+ kUnknown_Type = -1,
+
+ // !< The RR is empty
+ kEmpty_Type,
+
+ //!< The RR is actually a (non-empty) rect (i.e., at least one radius
+ //!< at each corner is zero)
+ kRect_Type,
+
+ //!< The RR is actually a (non-empty) oval (i.e., all x radii are equal
+ //!< and >= width/2 and all the y radii are equal and >= height/2
+ kOval_Type,
+
+ //!< The RR is non-empty and all the x radii are equal & all y radii
+ //!< are equal but it is not an oval (i.e., there are lines between
+ //!< the curves) nor a rect (i.e., both radii are non-zero)
+ kSimple_Type,
+
+ //!< A fully general (non-empty) RR. Some of the x and/or y radii are
+ //!< different from the others and there must be one corner where
+ //!< both radii are non-zero.
+ kComplex_Type,
+ };
+
+ /**
+ * Returns the RR's sub type.
+ */
+ Type getType() const {
+ SkDEBUGCODE(this->validate();)
+
+ if (kUnknown_Type == fType) {
+ this->computeType();
+ }
+ SkASSERT(kUnknown_Type != fType);
+ return fType;
+ }
+
+ Type type() const { return this->getType(); }
+
+ inline bool isEmpty() const { return kEmpty_Type == this->getType(); }
+ inline bool isRect() const { return kRect_Type == this->getType(); }
+ inline bool isOval() const { return kOval_Type == this->getType(); }
+ inline bool isSimple() const { return kSimple_Type == this->getType(); }
+ inline bool isComplex() const { return kComplex_Type == this->getType(); }
+
+ SkScalar width() const { return fRect.width(); }
+ SkScalar height() const { return fRect.height(); }
+
+ /**
+ * Set this RR to the empty rectangle (0,0,0,0) with 0 x & y radii.
+ */
+ void setEmpty() {
+ fRect.setEmpty();
+ memset(fRadii, 0, sizeof(fRadii));
+ fType = kEmpty_Type;
+
+ SkDEBUGCODE(this->validate();)
+ }
+
+ /**
+ * Set this RR to match the supplied rect. All radii will be 0.
+ */
+ void setRect(const SkRect& rect) {
+ if (rect.isEmpty()) {
+ this->setEmpty();
+ return;
+ }
+
+ fRect = rect;
+ memset(fRadii, 0, sizeof(fRadii));
+ fType = kRect_Type;
+
+ SkDEBUGCODE(this->validate();)
+ }
+
+ /**
+ * Set this RR to match the supplied oval. All x radii will equal half the
+ * width and all y radii will equal half the height.
+ */
+ void setOval(const SkRect& oval) {
+ if (oval.isEmpty()) {
+ this->setEmpty();
+ return;
+ }
+
+ SkScalar xRad = SkScalarHalf(oval.width());
+ SkScalar yRad = SkScalarHalf(oval.height());
+
+ fRect = oval;
+ for (int i = 0; i < 4; ++i) {
+ fRadii[i].set(xRad, yRad);
+ }
+ fType = kOval_Type;
+
+ SkDEBUGCODE(this->validate();)
+ }
+
+ /**
+ * Initialize the RR with the same radii for all four corners.
+ */
+ void setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad);
+
+ /**
+ * Initialize the RR with potentially different radii for all four corners.
+ */
+ void setRectRadii(const SkRect& rect, const SkVector radii[4]);
+
+ // The radii are stored in UL, UR, LR, LL order.
+ enum Corner {
+ kUpperLeft_Corner,
+ kUpperRight_Corner,
+ kLowerRight_Corner,
+ kLowerLeft_Corner
+ };
+
+ const SkRect& rect() const { return fRect; }
+ const SkVector& radii(Corner corner) const { return fRadii[corner]; }
+ const SkRect& getBounds() const { return fRect; }
+
+ /**
+ * When a rrect is simple, all of its radii are equal. This returns one
+ * of those radii. This call requires the rrect to be non-complex.
+ */
+ const SkVector& getSimpleRadii() const {
+ SkASSERT(!this->isComplex());
+ return fRadii[0];
+ }
+
+ friend bool operator==(const SkRRect& a, const SkRRect& b) {
+ return a.fRect == b.fRect &&
+ SkScalarsEqual((SkScalar*) a.fRadii, (SkScalar*) b.fRadii, 8);
+ }
+
+ friend bool operator!=(const SkRRect& a, const SkRRect& b) {
+ return a.fRect != b.fRect ||
+ !SkScalarsEqual((SkScalar*) a.fRadii, (SkScalar*) b.fRadii, 8);
+ }
+
+ /**
+ * Returns true if (p.fX,p.fY) is inside the RR, and the RR
+ * is not empty.
+ *
+ * Contains treats the left and top differently from the right and bottom.
+ * The left and top coordinates of the RR are themselves considered
+ * to be inside, while the right and bottom are not. All the points on the
+ * edges of the corners are considered to be inside.
+ */
+ bool contains(const SkPoint& p) const {
+ return contains(p.fX, p.fY);
+ }
+
+ /**
+ * Returns true if (x,y) is inside the RR, and the RR
+ * is not empty.
+ *
+ * Contains treats the left and top differently from the right and bottom.
+ * The left and top coordinates of the RR are themselves considered
+ * to be inside, while the right and bottom are not. All the points on the
+ * edges of the corners are considered to be inside.
+ */
+ bool contains(SkScalar x, SkScalar y) const;
+
+#if 0
+ void inset(SkScalar dx, SkScalar dy, SkRRect* dst) const;
+ void inset(SkScalar dx, SkScalar dy) {
+ this->inset(dx, dy, this);
+ }
+ void outset(SkScalar dx, SkScalar dy, SkRRect* dst) const {
+ this->inset(-dx, -dy, dst);
+ }
+ void outset(SkScalar dx, SkScalar dy) {
+ this->inset(-dx, -dy, this);
+ }
+#endif
+
+ SkDEBUGCODE(void validate() const;)
+
+ enum {
+ kSizeInMemory = 12 * sizeof(SkScalar)
+ };
+
+ /**
+ * Write the rrect into the specified buffer. This is guaranteed to always
+ * write kSizeInMemory bytes, and that value is guaranteed to always be
+ * a multiple of 4. Return kSizeInMemory.
+ */
+ uint32_t writeToMemory(void* buffer) const;
+
+ /**
+ * Read the rrect from the specified buffer. This is guaranteed to always
+ * read kSizeInMemory bytes, and that value is guaranteed to always be
+ * a multiple of 4. Return kSizeInMemory.
+ */
+ uint32_t readFromMemory(const void* buffer);
+
+private:
+ SkRect fRect;
+ // Radii order is UL, UR, LR, LL. Use Corner enum to index into fRadii[]
+ SkVector fRadii[4];
+ mutable Type fType;
+ // TODO: add padding so we can use memcpy for flattening and not copy
+ // uninitialized data
+
+ void computeType() const;
+
+ // to access fRadii directly
+ friend class SkPath;
+};
+
+#endif
diff --git a/include/core/SkRandom.h b/include/core/SkRandom.h
index 0f9b9aa003..73dc4917d7 100644
--- a/include/core/SkRandom.h
+++ b/include/core/SkRandom.h
@@ -40,6 +40,21 @@ public:
*/
S16CPU nextS16() { return this->nextS() >> 16; }
+ /**
+ * Returns value [0...1) as a float
+ */
+ float nextF() {
+ // const is 1 / (2^32 - 1)
+ return (float)(this->nextU() * 2.32830644e-10);
+ }
+
+ /**
+ * Returns value [min...max) as a float
+ */
+ float nextRangeF(float min, float max) {
+ return min + this->nextF() * (max - min);
+ }
+
/** Return the next pseudo random number, as an unsigned value of
at most bitCount bits.
@param bitCount The maximum number of bits to be returned
@@ -84,7 +99,7 @@ public:
in the range [min..max).
*/
SkScalar nextRangeScalar(SkScalar min, SkScalar max) {
- return SkScalarMul(this->nextSScalar1(), (max - min)) + min;
+ return SkScalarMul(this->nextUScalar1(), (max - min)) + min;
}
/** Return the next pseudo random number expressed as a SkScalar
@@ -96,6 +111,13 @@ public:
*/
bool nextBool() { return this->nextU() >= 0x80000000; }
+ /** A biased version of nextBool().
+ */
+ bool nextBiasedBool(SkScalar fractionTrue) {
+ SkASSERT(fractionTrue >= 0 && fractionTrue <= SK_Scalar1);
+ return this->nextUScalar1() <= fractionTrue;
+ }
+
/** Return the next pseudo random number as a signed 64bit value.
*/
void next64(Sk64* a) {
diff --git a/include/core/SkRasterizer.h b/include/core/SkRasterizer.h
index 5f71d3fd5c..3e662ab2c1 100644
--- a/include/core/SkRasterizer.h
+++ b/include/core/SkRasterizer.h
@@ -28,14 +28,14 @@ public:
*/
bool rasterize(const SkPath& path, const SkMatrix& matrix,
const SkIRect* clipBounds, SkMaskFilter* filter,
- SkMask* mask, SkMask::CreateMode mode);
+ SkMask* mask, SkMask::CreateMode mode) const;
protected:
SkRasterizer(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {}
virtual bool onRasterize(const SkPath& path, const SkMatrix& matrix,
const SkIRect* clipBounds,
- SkMask* mask, SkMask::CreateMode mode);
+ SkMask* mask, SkMask::CreateMode mode) const;
private:
typedef SkFlattenable INHERITED;
diff --git a/include/core/SkReader32.h b/include/core/SkReader32.h
index 4f6809fd9e..7a8d22a80c 100644
--- a/include/core/SkReader32.h
+++ b/include/core/SkReader32.h
@@ -13,6 +13,7 @@
#include "SkMatrix.h"
#include "SkPath.h"
#include "SkRegion.h"
+#include "SkRRect.h"
#include "SkScalar.h"
class SkString;
@@ -117,6 +118,11 @@ public:
(void)this->skip(size);
}
+ SkRRect* readRRect(SkRRect* rrect) {
+ rrect->readFromMemory(this->skip(SkRRect::kSizeInMemory));
+ return rrect;
+ }
+
void readRegion(SkRegion* rgn) {
size_t size = rgn->readFromMemory(this->peek());
SkASSERT(SkAlign4(size) == size);
diff --git a/include/core/SkRect.h b/include/core/SkRect.h
index 21a37364d4..21ea84ff58 100644
--- a/include/core/SkRect.h
+++ b/include/core/SkRect.h
@@ -72,6 +72,24 @@ struct SK_API SkIRect {
int height() const { return fBottom - fTop; }
/**
+ * Since the center of an integer rect may fall on a factional value, this
+ * method is defined to return (right + left) >> 1.
+ *
+ * This is a specific "truncation" of the average, which is different than
+ * (right + left) / 2 when the sum is negative.
+ */
+ int centerX() const { return (fRight + fLeft) >> 1; }
+
+ /**
+ * Since the center of an integer rect may fall on a factional value, this
+ * method is defined to return (bottom + top) >> 1
+ *
+ * This is a specific "truncation" of the average, which is different than
+ * (bottom + top) / 2 when the sum is negative.
+ */
+ int centerY() const { return (fBottom + fTop) >> 1; }
+
+ /**
* Return true if the rectangle's width or height are <= 0
*/
bool isEmpty() const { return fLeft >= fRight || fTop >= fBottom; }
@@ -142,6 +160,16 @@ struct SK_API SkIRect {
this->offset(delta.fX, delta.fY);
}
+ /**
+ * Offset this rect such its new x() and y() will equal newX and newY.
+ */
+ void offsetTo(int32_t newX, int32_t newY) {
+ fRight += newX - fLeft;
+ fBottom += newY - fTop;
+ fLeft = newX;
+ fTop = newY;
+ }
+
/** Inset the rectangle by (dx,dy). If dx is positive, then the sides are moved inwards,
making the rectangle narrower. If dx is negative, then the sides are moved outwards,
making the rectangle wider. The same holds true for dy and the top and bottom.
@@ -356,6 +384,15 @@ struct SK_API SkRect {
return r;
}
+ static SkRect SK_WARN_UNUSED_RESULT MakeFromIRect(const SkIRect& irect) {
+ SkRect r;
+ r.set(SkIntToScalar(irect.fLeft),
+ SkIntToScalar(irect.fTop),
+ SkIntToScalar(irect.fRight),
+ SkIntToScalar(irect.fBottom));
+ return r;
+ }
+
/**
* Return true if the rectangle's width or height are <= 0
*/
@@ -401,11 +438,11 @@ struct SK_API SkRect {
SkScalar centerY() const { return SkScalarHalf(fTop + fBottom); }
friend bool operator==(const SkRect& a, const SkRect& b) {
- return 0 == memcmp(&a, &b, sizeof(a));
+ return SkScalarsEqual((SkScalar*)&a, (SkScalar*)&b, 4);
}
friend bool operator!=(const SkRect& a, const SkRect& b) {
- return 0 != memcmp(&a, &b, sizeof(a));
+ return !SkScalarsEqual((SkScalar*)&a, (SkScalar*)&b, 4);
}
/** return the 4 points that enclose the rectangle
@@ -519,6 +556,16 @@ struct SK_API SkRect {
this->offset(delta.fX, delta.fY);
}
+ /**
+ * Offset this rect such its new x() and y() will equal newX and newY.
+ */
+ void offsetTo(SkScalar newX, SkScalar newY) {
+ fRight += newX - fLeft;
+ fBottom += newY - fTop;
+ fLeft = newX;
+ fTop = newY;
+ }
+
/** Inset the rectangle by (dx,dy). If dx is positive, then the sides are
moved inwards, making the rectangle narrower. If dx is negative, then
the sides are moved outwards, making the rectangle wider. The same holds
@@ -656,8 +703,8 @@ struct SK_API SkRect {
*/
void round(SkIRect* dst) const {
SkASSERT(dst);
- dst->set(SkScalarRound(fLeft), SkScalarRound(fTop),
- SkScalarRound(fRight), SkScalarRound(fBottom));
+ dst->set(SkScalarRoundToInt(fLeft), SkScalarRoundToInt(fTop),
+ SkScalarRoundToInt(fRight), SkScalarRoundToInt(fBottom));
}
/**
@@ -666,8 +713,8 @@ struct SK_API SkRect {
*/
void roundOut(SkIRect* dst) const {
SkASSERT(dst);
- dst->set(SkScalarFloor(fLeft), SkScalarFloor(fTop),
- SkScalarCeil(fRight), SkScalarCeil(fBottom));
+ dst->set(SkScalarFloorToInt(fLeft), SkScalarFloorToInt(fTop),
+ SkScalarCeilToInt(fRight), SkScalarCeilToInt(fBottom));
}
/**
@@ -683,6 +730,19 @@ struct SK_API SkRect {
}
/**
+ * Set the dst rectangle by rounding "in" this rectangle, choosing the
+ * ceil of top and left, and the floor of right and bottom. This does *not*
+ * call sort(), so it is possible that the resulting rect is inverted...
+ * e.g. left >= right or top >= bottom. Call isEmpty() to detect that.
+ */
+ void roundIn(SkIRect* dst) const {
+ SkASSERT(dst);
+ dst->set(SkScalarCeilToInt(fLeft), SkScalarCeilToInt(fTop),
+ SkScalarFloorToInt(fRight), SkScalarFloorToInt(fBottom));
+ }
+
+
+ /**
* Swap top/bottom or left/right if there are flipped (i.e. if width()
* or height() would have returned a negative value.) This should be called
* if the edges are computed separately, and may have crossed over each
diff --git a/include/core/SkRefCnt.h b/include/core/SkRefCnt.h
index 3a0f8be383..21ed0efec3 100644
--- a/include/core/SkRefCnt.h
+++ b/include/core/SkRefCnt.h
@@ -71,6 +71,16 @@ public:
SkASSERT(fRefCnt > 0);
}
+ /**
+ * Alias for ref(), for compatibility with scoped_refptr.
+ */
+ void AddRef() { this->ref(); }
+
+ /**
+ * Alias for unref(), for compatibility with scoped_refptr.
+ */
+ void Release() { this->unref(); }
+
protected:
/**
* Allow subclasses to call this if they've overridden internal_dispose
diff --git a/include/core/SkScalar.h b/include/core/SkScalar.h
index 8e8e73e318..bb437a0bf3 100644
--- a/include/core/SkScalar.h
+++ b/include/core/SkScalar.h
@@ -29,8 +29,6 @@
as a 16.16 fixed point integer.
*/
typedef float SkScalar;
- extern const uint32_t gIEEENotANumber;
- extern const uint32_t gIEEEInfinity;
/** SK_Scalar1 is defined to be 1.0 represented as an SkScalar
*/
@@ -40,7 +38,7 @@
#define SK_ScalarHalf (0.5f)
/** SK_ScalarInfinity is defined to be infinity as an SkScalar
*/
- #define SK_ScalarInfinity (*SkTCast<const float*>(&gIEEEInfinity))
+ #define SK_ScalarInfinity SK_FloatInfinity
/** SK_ScalarMax is defined to be the largest value representable as an SkScalar
*/
#define SK_ScalarMax (3.402823466e+38f)
@@ -49,7 +47,7 @@
#define SK_ScalarMin (-SK_ScalarMax)
/** SK_ScalarNaN is defined to be 'Not a Number' as an SkScalar
*/
- #define SK_ScalarNaN (*SkTCast<const float*>(&gIEEENotANumber))
+ #define SK_ScalarNaN SK_FloatNaN
/** SkScalarIsNaN(n) returns true if argument is not a number
*/
static inline bool SkScalarIsNaN(float x) { return x != x; }
@@ -355,4 +353,21 @@ static inline SkScalar SkScalarLog2(SkScalar x) {
SkScalar SkScalarInterpFunc(SkScalar searchKey, const SkScalar keys[],
const SkScalar values[], int length);
+/*
+ * Helper to compare an array of scalars.
+ */
+static inline bool SkScalarsEqual(const SkScalar a[], const SkScalar b[], int n) {
+#ifdef SK_SCALAR_IS_FLOAT
+ SkASSERT(n >= 0);
+ for (int i = 0; i < n; ++i) {
+ if (a[i] != b[i]) {
+ return false;
+ }
+ }
+ return true;
+#else
+ return 0 == memcmp(a, b, n * sizeof(SkScalar));
+#endif
+}
+
#endif
diff --git a/include/core/SkShader.h b/include/core/SkShader.h
index a0a1b056d4..389be351b2 100644
--- a/include/core/SkShader.h
+++ b/include/core/SkShader.h
@@ -18,8 +18,8 @@
class SkPath;
class GrContext;
-class GrCustomStage;
-class GrSamplerState;
+class GrEffect;
+class GrEffectStage;
/** \class SkShader
*
@@ -39,22 +39,25 @@ public:
virtual ~SkShader();
/**
- * Return true if the shader has a non-identity local matrix.
- * @param localM Optional: If not null, return the shader's local matrix
- * @return true if the shader has a non-identity local matrix.
+ * Returns true if the local matrix is not an identity matrix.
*/
- bool getLocalMatrix(SkMatrix* localM) const;
+ bool hasLocalMatrix() const { return !fLocalMatrix.isIdentity(); }
+
+ /**
+ * Returns the local matrix.
+ */
+ const SkMatrix& getLocalMatrix() const { return fLocalMatrix; }
/**
* Set the shader's local matrix.
* @param localM The shader's new local matrix.
*/
- void setLocalMatrix(const SkMatrix& localM);
+ void setLocalMatrix(const SkMatrix& localM) { fLocalMatrix = localM; }
/**
* Reset the shader's local matrix to identity.
*/
- void resetLocalMatrix();
+ void resetLocalMatrix() { fLocalMatrix.reset(); }
enum TileMode {
/** replicate the edge color if the shader draws outside of its
@@ -136,12 +139,29 @@ public:
/**
* Called once before drawing, with the current paint and device matrix.
* Return true if your shader supports these parameters, or false if not.
- * If false is returned, nothing will be drawn.
+ * If false is returned, nothing will be drawn. If true is returned, then
+ * a balancing call to endContext() will be made before the next call to
+ * setContext.
+ *
+ * Subclasses should be sure to call their INHERITED::setContext() if they
+ * override this method.
*/
virtual bool setContext(const SkBitmap& device, const SkPaint& paint,
const SkMatrix& matrix);
/**
+ * Assuming setContext returned true, endContext() will be called when
+ * the draw using the shader has completed. It is an error for setContext
+ * to be called twice w/o an intervening call to endContext().
+ *
+ * Subclasses should be sure to call their INHERITED::endContext() if they
+ * override this method.
+ */
+ virtual void endContext();
+
+ SkDEBUGCODE(bool setContextHasBeenCalled() const { return SkToBool(fInSetContext); })
+
+ /**
* Called for each span of the object being drawn. Your subclass should
* set the appropriate colors (with premultiplied alpha) that correspond
* to the specified device coordinates.
@@ -180,14 +200,6 @@ public:
}
/**
- * Called before a session using the shader begins. Some shaders override
- * this to defer some of their work (like calling bitmap.lockPixels()).
- * Must be balanced by a call to endSession.
- */
- virtual void beginSession();
- virtual void endSession();
-
- /**
Gives method bitmap should be read to implement a shader.
Also determines number and interpretation of "extra" parameters returned
by asABitmap
@@ -306,13 +318,12 @@ public:
virtual GradientType asAGradient(GradientInfo* info) const;
/**
- * If the shader subclass has a GrCustomStage implementation, this installs
- * a custom stage on the sampler. A GrContext pointer is required since custom
- * stages may need to create textures. The sampler parameter is necessary to set a
- * texture matrix. It will eventually be removed and this function will operate as a
- * GrCustomStage factory.
+ * If the shader subclass has a GrEffect implementation, this installs an effect on the stage.
+ * A GrContext pointer is required since effects may need to create textures. The stage
+ * parameter is necessary to set a texture matrix. It will eventually be removed and this
+ * function will operate as a GrEffect factory.
*/
- virtual bool asNewCustomStage(GrContext* context, GrSamplerState* sampler) const;
+ virtual bool asNewEffect(GrContext* context, GrEffectStage* stage) const;
//////////////////////////////////////////////////////////////////////////
// Factory methods for stock shaders
@@ -348,12 +359,12 @@ protected:
SkShader(SkFlattenableReadBuffer& );
virtual void flatten(SkFlattenableWriteBuffer&) const SK_OVERRIDE;
private:
- SkMatrix* fLocalMatrix;
+ SkMatrix fLocalMatrix;
SkMatrix fTotalInverse;
uint8_t fPaintAlpha;
uint8_t fDeviceConfig;
uint8_t fTotalInverseClass;
- SkDEBUGCODE(SkBool8 fInSession;)
+ SkDEBUGCODE(SkBool8 fInSetContext;)
static SkShader* CreateBitmapShader(const SkBitmap& src,
TileMode, TileMode,
diff --git a/include/core/SkSize.h b/include/core/SkSize.h
index 808583f3e8..01c6e35083 100644
--- a/include/core/SkSize.h
+++ b/include/core/SkSize.h
@@ -1,4 +1,3 @@
-
/*
* Copyright 2011 Google Inc.
*
@@ -6,10 +5,11 @@
* found in the LICENSE file.
*/
-
#ifndef SkSize_DEFINED
#define SkSize_DEFINED
+#include "SkScalar.h"
+
template <typename T> struct SkTSize {
T fWidth;
T fHeight;
@@ -74,8 +74,6 @@ static inline bool operator!=(const SkTSize<T>& a, const SkTSize<T>& b) {
typedef SkTSize<int32_t> SkISize;
-#include "SkScalar.h"
-
struct SkSize : public SkTSize<SkScalar> {
static SkSize Make(SkScalar w, SkScalar h) {
SkSize s;
diff --git a/include/core/SkString.h b/include/core/SkString.h
index 5896ef6b70..94dcf8b438 100644
--- a/include/core/SkString.h
+++ b/include/core/SkString.h
@@ -15,18 +15,30 @@
/* Some helper functions for C strings
*/
-static bool SkStrStartsWith(const char string[], const char prefix[]) {
+static bool SkStrStartsWith(const char string[], const char prefixStr[]) {
SkASSERT(string);
- SkASSERT(prefix);
- return !strncmp(string, prefix, strlen(prefix));
+ SkASSERT(prefixStr);
+ return !strncmp(string, prefixStr, strlen(prefixStr));
}
-bool SkStrEndsWith(const char string[], const char suffix[]);
+static bool SkStrStartsWith(const char string[], const char prefixChar) {
+ SkASSERT(string);
+ return (prefixChar == *string);
+}
+
+bool SkStrEndsWith(const char string[], const char suffixStr[]);
+bool SkStrEndsWith(const char string[], const char suffixChar);
+
int SkStrStartsWithOneOf(const char string[], const char prefixes[]);
+
static bool SkStrContains(const char string[], const char substring[]) {
SkASSERT(string);
SkASSERT(substring);
return (NULL != strstr(string, substring));
}
+static bool SkStrContains(const char string[], const char subchar) {
+ SkASSERT(string);
+ return (NULL != strchr(string, subchar));
+}
#define SkStrAppendS32_MaxSize 11
char* SkStrAppendS32(char buffer[], int32_t);
@@ -64,7 +76,7 @@ char* SkStrAppendFixed(char buffer[], SkFixed);
counting to make string assignments and copies very fast
with no extra RAM cost. Assumes UTF8 encoding.
*/
-class SkString {
+class SK_API SkString {
public:
SkString();
explicit SkString(size_t len);
@@ -82,15 +94,24 @@ public:
bool equals(const char text[]) const;
bool equals(const char text[], size_t len) const;
- bool startsWith(const char prefix[]) const {
- return SkStrStartsWith(fRec->data(), prefix);
+ bool startsWith(const char prefixStr[]) const {
+ return SkStrStartsWith(fRec->data(), prefixStr);
+ }
+ bool startsWith(const char prefixChar) const {
+ return SkStrStartsWith(fRec->data(), prefixChar);
}
- bool endsWith(const char suffix[]) const {
- return SkStrEndsWith(fRec->data(), suffix);
+ bool endsWith(const char suffixStr[]) const {
+ return SkStrEndsWith(fRec->data(), suffixStr);
+ }
+ bool endsWith(const char suffixChar) const {
+ return SkStrEndsWith(fRec->data(), suffixChar);
}
bool contains(const char substring[]) const {
return SkStrContains(fRec->data(), substring);
}
+ bool contains(const char subchar) const {
+ return SkStrContains(fRec->data(), subchar);
+ }
friend bool operator==(const SkString& a, const SkString& b) {
return a.equals(b);
diff --git a/include/core/SkStrokeRec.h b/include/core/SkStrokeRec.h
new file mode 100644
index 0000000000..c5b47c25dd
--- /dev/null
+++ b/include/core/SkStrokeRec.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkStrokeRec_DEFINED
+#define SkStrokeRec_DEFINED
+
+#include "SkPaint.h"
+
+class SkPath;
+
+class SkStrokeRec {
+public:
+ enum InitStyle {
+ kHairline_InitStyle,
+ kFill_InitStyle
+ };
+ SkStrokeRec(InitStyle style);
+
+ SkStrokeRec(const SkStrokeRec&);
+ explicit SkStrokeRec(const SkPaint&);
+
+ enum Style {
+ kHairline_Style,
+ kFill_Style,
+ kStroke_Style,
+ kStrokeAndFill_Style
+ };
+
+ Style getStyle() const;
+ SkScalar getWidth() const { return fWidth; }
+ SkScalar getMiter() const { return fMiterLimit; }
+ SkPaint::Cap getCap() const { return fCap; }
+ SkPaint::Join getJoin() const { return fJoin; }
+
+ bool isHairlineStyle() const {
+ return kHairline_Style == this->getStyle();
+ }
+
+ bool isFillStyle() const {
+ return kFill_Style == this->getStyle();
+ }
+
+ void setFillStyle();
+ void setHairlineStyle();
+ /**
+ * Specify the strokewidth, and optionally if you want stroke + fill.
+ * Note, if width==0, then this request is taken to mean:
+ * strokeAndFill==true -> new style will be Fill
+ * strokeAndFill==false -> new style will be Hairline
+ */
+ void setStrokeStyle(SkScalar width, bool strokeAndFill = false);
+
+ void setStrokeParams(SkPaint::Cap cap, SkPaint::Join join, SkScalar miterLimit) {
+ fCap = cap;
+ fJoin = join;
+ fMiterLimit = miterLimit;
+ }
+
+ /**
+ * Returns true if this specifes any thick stroking, i.e. applyToPath()
+ * will return true.
+ */
+ bool needToApply() const {
+ Style style = this->getStyle();
+ return (kStroke_Style == style) || (kStrokeAndFill_Style == style);
+ }
+
+ /**
+ * Apply these stroke parameters to the src path, returning the result
+ * in dst.
+ *
+ * If there was no change (i.e. style == hairline or fill) this returns
+ * false and dst is unchanged. Otherwise returns true and the result is
+ * stored in dst.
+ *
+ * src and dst may be the same path.
+ */
+ bool applyToPath(SkPath* dst, const SkPath& src) const;
+
+private:
+ SkScalar fWidth;
+ SkScalar fMiterLimit;
+ SkPaint::Cap fCap;
+ SkPaint::Join fJoin;
+ bool fStrokeAndFill;
+};
+
+#endif
diff --git a/include/core/SkSurface.h b/include/core/SkSurface.h
index 18ae5ccdc7..70012994db 100644
--- a/include/core/SkSurface.h
+++ b/include/core/SkSurface.h
@@ -35,8 +35,7 @@ public:
* If the requested surface cannot be created, or the request is not a
* supported configuration, NULL will be returned.
*/
- static SkSurface* NewRasterDirect(const SkImage::Info&, SkColorSpace*,
- void* pixels, size_t rowBytes);
+ static SkSurface* NewRasterDirect(const SkImage::Info&, void* pixels, size_t rowBytes);
/**
* Return a new surface, with the memory for the pixels automatically
@@ -45,7 +44,7 @@ public:
* If the requested surface cannot be created, or the request is not a
* supported configuration, NULL will be returned.
*/
- static SkSurface* NewRaster(const SkImage::Info&, SkColorSpace*);
+ static SkSurface* NewRaster(const SkImage::Info&);
/**
* Return a new surface whose contents will be recorded into a picture.
@@ -63,8 +62,7 @@ public:
* Return a new surface whose contents will be drawn to an offscreen
* render target, allocated by the surface.
*/
- static SkSurface* NewRenderTarget(GrContext*, const SkImage::Info&,
- SkColorSpace*, int sampleCount = 0);
+ static SkSurface* NewRenderTarget(GrContext*, const SkImage::Info&, int sampleCount = 0);
int width() const { return fWidth; }
int height() const { return fHeight; }
@@ -107,7 +105,7 @@ public:
* ... // draw using canvasB
* canvasA->drawSurface(surfaceB); // <--- this will always be optimal!
*/
- SkSurface* newSurface(const SkImage::Info&, SkColorSpace*);
+ SkSurface* newSurface(const SkImage::Info&);
/**
* Returns an image of the current state of the surface pixels up to this
diff --git a/include/core/SkTDLinkedList.h b/include/core/SkTDLinkedList.h
deleted file mode 100644
index 92478121bb..0000000000
--- a/include/core/SkTDLinkedList.h
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright 2012 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SkTDLinkedList_DEFINED
-#define SkTDLinkedList_DEFINED
-
-#include "SkTypes.h"
-
-/**
- * Helper class to automatically initialize the doubly linked list
- * created pointers.
- */
-template <typename T> class SkPtrWrapper {
- public:
- SkPtrWrapper() : fPtr(NULL) {}
- SkPtrWrapper& operator =(T* ptr) { fPtr = ptr; return *this; }
- operator T*() const { return fPtr; }
- T* operator->() { return fPtr; }
- private:
- T* fPtr;
-};
-
-
-/**
- * This macro creates the member variables required by
- * the SkTDLinkedList class. It should be placed in the private section
- * of any class that will be stored in a double linked list.
- */
-#define SK_DEFINE_DLINKEDLIST_INTERFACE(ClassName) \
- friend class SkTDLinkedList<ClassName>; \
- /* back pointer to the owning list - for debugging */ \
- SkDEBUGCODE(SkPtrWrapper<SkTDLinkedList<ClassName> > fList;)\
- SkPtrWrapper<ClassName> fPrev; \
- SkPtrWrapper<ClassName> fNext;
-
-/**
- * This class implements a templated internal doubly linked list data structure.
- */
-template <class T> class SkTDLinkedList : public SkNoncopyable {
-public:
- SkTDLinkedList()
- : fHead(NULL)
- , fTail(NULL) {
- }
-
- void remove(T* entry) {
- SkASSERT(NULL != fHead && NULL != fTail);
- SkASSERT(this->isInList(entry));
-
- T* prev = entry->fPrev;
- T* next = entry->fNext;
-
- if (NULL != prev) {
- prev->fNext = next;
- } else {
- fHead = next;
- }
- if (NULL != next) {
- next->fPrev = prev;
- } else {
- fTail = prev;
- }
-
- entry->fPrev = NULL;
- entry->fNext = NULL;
-
-#ifdef SK_DEBUG
- entry->fList = NULL;
-#endif
- }
-
- void addToHead(T* entry) {
- SkASSERT(NULL == entry->fPrev && NULL == entry->fNext);
- SkASSERT(NULL == entry->fList);
-
- entry->fPrev = NULL;
- entry->fNext = fHead;
- if (NULL != fHead) {
- fHead->fPrev = entry;
- }
- fHead = entry;
- if (NULL == fTail) {
- fTail = entry;
- }
-
-#ifdef SK_DEBUG
- entry->fList = this;
-#endif
- }
-
- bool isEmpty() const {
- return NULL == fHead && NULL == fTail;
- }
-
- T* head() { return fHead; }
- T* tail() { return fTail; }
-
- class Iter {
- public:
- enum IterStart {
- kHead_IterStart,
- kTail_IterStart
- };
-
- Iter() : fCur(NULL) {}
-
- T* init(SkTDLinkedList& list, IterStart startLoc) {
- if (kHead_IterStart == startLoc) {
- fCur = list.fHead;
- } else {
- SkASSERT(kTail_IterStart == startLoc);
- fCur = list.fTail;
- }
-
- return fCur;
- }
-
- /**
- * Return the next/previous element in the list or NULL if at the end.
- */
- T* next() {
- if (NULL == fCur) {
- return NULL;
- }
-
- fCur = fCur->fNext;
- return fCur;
- }
-
- T* prev() {
- if (NULL == fCur) {
- return NULL;
- }
-
- fCur = fCur->fPrev;
- return fCur;
- }
-
- private:
- T* fCur;
- };
-
-#ifdef SK_DEBUG
- void validate() const {
- GrAssert(!fHead == !fTail);
- }
-
- /**
- * Debugging-only method that uses the list back pointer to check if
- * 'entry' is indeed in 'this' list.
- */
- bool isInList(const T* entry) const {
- return entry->fList == this;
- }
-
- /**
- * Debugging-only method that laboriously counts the list entries.
- */
- int countEntries() const {
- int count = 0;
- for (T* entry = fHead; NULL != entry; entry = entry->fNext) {
- ++count;
- }
- return count;
- }
-#endif // SK_DEBUG
-
-private:
- T* fHead;
- T* fTail;
-
- typedef SkNoncopyable INHERITED;
-};
-
-#endif // SkTDLinkedList_DEFINED
diff --git a/include/core/SkTInternalLList.h b/include/core/SkTInternalLList.h
new file mode 100644
index 0000000000..a6b6f15353
--- /dev/null
+++ b/include/core/SkTInternalLList.h
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkTInternalLList_DEFINED
+#define SkTInternalLList_DEFINED
+
+#include "SkTypes.h"
+
+/**
+ * Helper class to automatically initialize the doubly linked list created pointers.
+ */
+template <typename T> class SkPtrWrapper {
+ public:
+ SkPtrWrapper() : fPtr(NULL) {}
+ SkPtrWrapper& operator =(T* ptr) { fPtr = ptr; return *this; }
+ operator T*() const { return fPtr; }
+ T* operator->() { return fPtr; }
+ private:
+ T* fPtr;
+};
+
+
+/**
+ * This macro creates the member variables required by the SkTInternalLList class. It should be
+ * placed in the private section of any class that will be stored in a double linked list.
+ */
+#define SK_DECLARE_INTERNAL_LLIST_INTERFACE(ClassName) \
+ friend class SkTInternalLList<ClassName>; \
+ /* back pointer to the owning list - for debugging */ \
+ SkDEBUGCODE(SkPtrWrapper<SkTInternalLList<ClassName> > fList;) \
+ SkPtrWrapper<ClassName> fPrev; \
+ SkPtrWrapper<ClassName> fNext
+
+/**
+ * This class implements a templated internal doubly linked list data structure.
+ */
+template <class T> class SkTInternalLList : public SkNoncopyable {
+public:
+ SkTInternalLList()
+ : fHead(NULL)
+ , fTail(NULL) {
+ }
+
+ void remove(T* entry) {
+ SkASSERT(NULL != fHead && NULL != fTail);
+ SkASSERT(this->isInList(entry));
+
+ T* prev = entry->fPrev;
+ T* next = entry->fNext;
+
+ if (NULL != prev) {
+ prev->fNext = next;
+ } else {
+ fHead = next;
+ }
+ if (NULL != next) {
+ next->fPrev = prev;
+ } else {
+ fTail = prev;
+ }
+
+ entry->fPrev = NULL;
+ entry->fNext = NULL;
+
+#ifdef SK_DEBUG
+ entry->fList = NULL;
+#endif
+ }
+
+ void addToHead(T* entry) {
+ SkASSERT(NULL == entry->fPrev && NULL == entry->fNext);
+ SkASSERT(NULL == entry->fList);
+
+ entry->fPrev = NULL;
+ entry->fNext = fHead;
+ if (NULL != fHead) {
+ fHead->fPrev = entry;
+ }
+ fHead = entry;
+ if (NULL == fTail) {
+ fTail = entry;
+ }
+
+#ifdef SK_DEBUG
+ entry->fList = this;
+#endif
+ }
+
+ void addToTail(T* entry) {
+ SkASSERT(NULL == entry->fPrev && NULL == entry->fNext);
+ SkASSERT(NULL == entry->fList);
+
+ entry->fPrev = fTail;
+ entry->fNext = NULL;
+ if (NULL != fTail) {
+ fTail->fNext = entry;
+ }
+ fTail = entry;
+ if (NULL == fHead) {
+ fHead = entry;
+ }
+
+#ifdef SK_DEBUG
+ entry->fList = this;
+#endif
+ }
+
+ /**
+ * Inserts a new list entry before an existing list entry. The new entry must not already be
+ * a member of this or any other list. If existingEntry is NULL then the new entry is added
+ * at the tail.
+ */
+ void addBefore(T* newEntry, T* existingEntry) {
+ SkASSERT(NULL != newEntry);
+
+ if (NULL == existingEntry) {
+ this->addToTail(newEntry);
+ return;
+ }
+
+ SkASSERT(this->isInList(existingEntry));
+ newEntry->fNext = existingEntry;
+ T* prev = existingEntry->fPrev;
+ existingEntry->fPrev = newEntry;
+ newEntry->fPrev = prev;
+ if (NULL == prev) {
+ SkASSERT(fHead == existingEntry);
+ fHead = newEntry;
+ } else {
+ prev->fNext = newEntry;
+ }
+#ifdef SK_DEBUG
+ newEntry->fList = this;
+#endif
+ }
+
+ /**
+ * Inserts a new list entry after an existing list entry. The new entry must not already be
+ * a member of this or any other list. If existingEntry is NULL then the new entry is added
+ * at the head.
+ */
+ void addAfter(T* newEntry, T* existingEntry) {
+ SkASSERT(NULL != newEntry);
+
+ if (NULL == existingEntry) {
+ this->addToHead(newEntry);
+ return;
+ }
+
+ SkASSERT(this->isInList(existingEntry));
+ newEntry->fPrev = existingEntry;
+ T* next = existingEntry->fNext;
+ existingEntry->fNext = newEntry;
+ newEntry->fNext = next;
+ if (NULL == next) {
+ SkASSERT(fTail == existingEntry);
+ fTail = newEntry;
+ } else {
+ next->fPrev = newEntry;
+ }
+#ifdef SK_DEBUG
+ newEntry->fList = this;
+#endif
+ }
+
+ bool isEmpty() const {
+ return NULL == fHead && NULL == fTail;
+ }
+
+ T* head() { return fHead; }
+ T* tail() { return fTail; }
+
+ class Iter {
+ public:
+ enum IterStart {
+ kHead_IterStart,
+ kTail_IterStart
+ };
+
+ Iter() : fCurr(NULL) {}
+ Iter(const Iter& iter) : fCurr(iter.fCurr) {}
+ Iter& operator= (const Iter& iter) { fCurr = iter.fCurr; return *this; }
+
+ T* init(const SkTInternalLList& list, IterStart startLoc) {
+ if (kHead_IterStart == startLoc) {
+ fCurr = list.fHead;
+ } else {
+ SkASSERT(kTail_IterStart == startLoc);
+ fCurr = list.fTail;
+ }
+
+ return fCurr;
+ }
+
+ T* get() { return fCurr; }
+
+ /**
+ * Return the next/previous element in the list or NULL if at the end.
+ */
+ T* next() {
+ if (NULL == fCurr) {
+ return NULL;
+ }
+
+ fCurr = fCurr->fNext;
+ return fCurr;
+ }
+
+ T* prev() {
+ if (NULL == fCurr) {
+ return NULL;
+ }
+
+ fCurr = fCurr->fPrev;
+ return fCurr;
+ }
+
+ private:
+ T* fCurr;
+ };
+
+#ifdef SK_DEBUG
+ void validate() const {
+ SkASSERT(!fHead == !fTail);
+ Iter iter;
+ for (T* item = iter.init(*this, Iter::kHead_IterStart); NULL != (item = iter.next()); ) {
+ SkASSERT(this->isInList(item));
+ if (NULL == item->fPrev) {
+ SkASSERT(fHead == item);
+ } else {
+ SkASSERT(item->fPrev->fNext == item);
+ }
+ if (NULL == item->fNext) {
+ SkASSERT(fTail == item);
+ } else {
+ SkASSERT(item->fNext->fPrev == item);
+ }
+ }
+ }
+
+ /**
+ * Debugging-only method that uses the list back pointer to check if 'entry' is indeed in 'this'
+ * list.
+ */
+ bool isInList(const T* entry) const {
+ return entry->fList == this;
+ }
+
+ /**
+ * Debugging-only method that laboriously counts the list entries.
+ */
+ int countEntries() const {
+ int count = 0;
+ for (T* entry = fHead; NULL != entry; entry = entry->fNext) {
+ ++count;
+ }
+ return count;
+ }
+#endif // SK_DEBUG
+
+private:
+ T* fHead;
+ T* fTail;
+
+ typedef SkNoncopyable INHERITED;
+};
+
+#endif
diff --git a/include/core/SkTLazy.h b/include/core/SkTLazy.h
index cd1e8f2b9c..315bd95101 100644
--- a/include/core/SkTLazy.h
+++ b/include/core/SkTLazy.h
@@ -116,10 +116,21 @@ class SkTCopyOnFirstWrite {
public:
SkTCopyOnFirstWrite(const T& initial) : fObj(&initial) {}
+ // Constructor for delayed initialization.
+ SkTCopyOnFirstWrite() : fObj(NULL) {}
+
+ // Should only be called once, and only if the default constructor was used.
+ void init(const T& initial) {
+ SkASSERT(NULL == fObj);
+ SkASSERT(!fLazy.isValid());
+ fObj = &initial;
+ }
+
/**
* Returns a writable T*. The first time this is called the initial object is cloned.
*/
T* writable() {
+ SkASSERT(NULL != fObj);
if (!fLazy.isValid()) {
fLazy.set(*fObj);
fObj = fLazy.get();
diff --git a/include/core/SkTemplates.h b/include/core/SkTemplates.h
index b7831ea961..b390a1965d 100644
--- a/include/core/SkTemplates.h
+++ b/include/core/SkTemplates.h
@@ -11,6 +11,7 @@
#define SkTemplates_DEFINED
#include "SkTypes.h"
+#include <new>
/** \file SkTemplates.h
diff --git a/include/core/SkTileGridPicture.h b/include/core/SkTileGridPicture.h
new file mode 100644
index 0000000000..b35c2a31d4
--- /dev/null
+++ b/include/core/SkTileGridPicture.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkTileGridPicture_DEFINED
+#define SkTileGridPicture_DEFINED
+
+#include "SkPicture.h"
+
+/**
+ * Subclass of SkPicture that override the behavior of the
+ * kOptimizeForClippedPlayback_RecordingFlag by creating an SkTileGrid
+ * structure rather than an R-Tree. The tile grid has lower recording
+ * and playback costs, but is less effective at eliminating extraneous
+ * primitives for arbitrary query rectangles. It is most effective for
+ * tiled playback when the tile structure is known at record time.
+ */
+class SkTileGridPicture : public SkPicture {
+public:
+ SkTileGridPicture(int tileWidth, int tileHeight, int width, int height);
+ virtual SkBBoxHierarchy* createBBoxHierarchy() const SK_OVERRIDE;
+private:
+ int fTileWidth, fTileHeight, fXTileCount, fYTileCount;
+};
+
+#endif
diff --git a/include/core/SkTypes.h b/include/core/SkTypes.h
index d44c560412..1c80f0e7e1 100644
--- a/include/core/SkTypes.h
+++ b/include/core/SkTypes.h
@@ -112,17 +112,30 @@ inline void operator delete(void* p) {
#define SkAssertResult(cond) cond
#endif
-namespace {
-
template <bool>
struct SkCompileAssert {
};
-} // namespace
-
#define SK_COMPILE_ASSERT(expr, msg) \
typedef SkCompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1]
+/*
+ * Usage: SK_MACRO_CONCAT(a, b) to construct the symbol ab
+ *
+ * SK_MACRO_CONCAT_IMPL_PRIV just exists to make this work. Do not use directly
+ *
+ */
+#define SK_MACRO_CONCAT(X, Y) SK_MACRO_CONCAT_IMPL_PRIV(X, Y)
+#define SK_MACRO_CONCAT_IMPL_PRIV(X, Y) X ## Y
+
+/*
+ * Usage: SK_MACRO_APPEND_LINE(foo) to make foo123, where 123 is the current
+ * line number. Easy way to construct
+ * unique names for local functions or
+ * variables.
+ */
+#define SK_MACRO_APPEND_LINE(name) SK_MACRO_CONCAT(name, __LINE__)
+
///////////////////////////////////////////////////////////////////////
/**
diff --git a/include/core/SkUserConfig.h b/include/core/SkUserConfig.h
index aeb6cc5a77..6c0b84ef07 100644
--- a/include/core/SkUserConfig.h
+++ b/include/core/SkUserConfig.h
@@ -78,6 +78,15 @@
//#define SK_DEBUG
//#define SK_RELEASE
+/* Skia has certain debug-only code that is extremely intensive even for debug
+ builds. This code is useful for diagnosing specific issues, but is not
+ generally applicable, therefore it must be explicitly enabled to avoid
+ the performance impact. By default these flags are undefined, but can be
+ enabled by uncommenting them below.
+ */
+//#define SK_DEBUG_GLYPH_CACHE
+//#define SK_DEBUG_PATH
+
/* To assist debugging, Skia provides an instance counting utility in
include/core/SkInstCount.h. This flag turns on and off that utility to
allow instance count tracking in either debug or release builds. By
@@ -209,4 +218,5 @@
backend. Defaults to 1 (build the GPU code).
*/
//#define SK_SUPPORT_GPU 1
+
#endif
diff --git a/include/core/SkWriter32.h b/include/core/SkWriter32.h
index 9354d6d16d..3f5a2433ed 100644
--- a/include/core/SkWriter32.h
+++ b/include/core/SkWriter32.h
@@ -16,6 +16,7 @@
#include "SkPath.h"
#include "SkPoint.h"
#include "SkRect.h"
+#include "SkRRect.h"
#include "SkMatrix.h"
#include "SkRegion.h"
@@ -37,6 +38,7 @@ public:
fSize(0),
fSingleBlock(NULL),
fSingleBlockSize(0),
+ fWrittenBeforeLastBlock(0),
fHead(NULL),
fTail(NULL),
fHeadIsExternalStorage(false) {}
@@ -108,6 +110,10 @@ public:
*(SkRect*)this->reserve(sizeof(rect)) = rect;
}
+ void writeRRect(const SkRRect& rrect) {
+ rrect.writeToMemory(this->reserve(SkRRect::kSizeInMemory));
+ }
+
void writePath(const SkPath& path) {
size_t size = path.writeToMemory(NULL);
SkASSERT(SkAlign4(size) == size);
@@ -197,6 +203,9 @@ private:
char* fSingleBlock;
uint32_t fSingleBlockSize;
+ // sum of bytes written in all blocks *before* fTail
+ uint32_t fWrittenBeforeLastBlock;
+
struct Block;
Block* fHead;
Block* fTail;
diff --git a/include/core/SkXfermode.h b/include/core/SkXfermode.h
index 99d5a9cc12..7fc1d501a5 100644
--- a/include/core/SkXfermode.h
+++ b/include/core/SkXfermode.h
@@ -28,13 +28,13 @@ public:
SkXfermode() {}
virtual void xfer32(SkPMColor dst[], const SkPMColor src[], int count,
- const SkAlpha aa[]);
+ const SkAlpha aa[]) const;
virtual void xfer16(uint16_t dst[], const SkPMColor src[], int count,
- const SkAlpha aa[]);
+ const SkAlpha aa[]) const;
virtual void xfer4444(uint16_t dst[], const SkPMColor src[], int count,
- const SkAlpha aa[]);
+ const SkAlpha aa[]) const;
virtual void xferA8(SkAlpha dst[], const SkPMColor src[], int count,
- const SkAlpha aa[]);
+ const SkAlpha aa[]) const;
/** Enum of possible coefficients to describe some xfermodes
*/
@@ -68,13 +68,13 @@ public:
srcover one isa
dstover ida one
*/
- virtual bool asCoeff(Coeff* src, Coeff* dst);
+ virtual bool asCoeff(Coeff* src, Coeff* dst) const;
/**
* The same as calling xfermode->asCoeff(..), except that this also checks
* if the xfermode is NULL, and if so, treats its as kSrcOver_Mode.
*/
- static bool AsCoeff(SkXfermode*, Coeff* src, Coeff* dst);
+ static bool AsCoeff(const SkXfermode*, Coeff* src, Coeff* dst);
/** List of predefined xfermodes.
The algebra for the modes uses the following symbols:
@@ -125,13 +125,13 @@ public:
* returns true and sets (if not null) mode accordingly. Otherwise it
* returns false and ignores the mode parameter.
*/
- virtual bool asMode(Mode* mode);
+ virtual bool asMode(Mode* mode) const;
/**
* The same as calling xfermode->asMode(mode), except that this also checks
* if the xfermode is NULL, and if so, treats its as kSrcOver_Mode.
*/
- static bool AsMode(SkXfermode*, Mode* mode);
+ static bool AsMode(const SkXfermode*, Mode* mode);
/**
* Returns true if the xfermode claims to be the specified Mode. This works
@@ -143,7 +143,7 @@ public:
* ...
* }
*/
- static bool IsMode(SkXfermode* xfer, Mode mode);
+ static bool IsMode(const SkXfermode* xfer, Mode mode);
/** Return an SkXfermode object for the specified mode.
*/
@@ -170,7 +170,7 @@ public:
static bool ModeAsCoeff(Mode mode, Coeff* src, Coeff* dst);
// DEPRECATED: call AsMode(...)
- static bool IsMode(SkXfermode* xfer, Mode* mode) {
+ static bool IsMode(const SkXfermode* xfer, Mode* mode) {
return AsMode(xfer, mode);
}
@@ -186,7 +186,7 @@ protected:
This method will not be called directly by the client, so it need not
be implemented if your subclass has overridden xfer32/xfer16/xferA8
*/
- virtual SkPMColor xferColor(SkPMColor src, SkPMColor dst);
+ virtual SkPMColor xferColor(SkPMColor src, SkPMColor dst) const;
private:
enum {
@@ -208,13 +208,13 @@ public:
// overrides from SkXfermode
virtual void xfer32(SkPMColor dst[], const SkPMColor src[], int count,
- const SkAlpha aa[]) SK_OVERRIDE;
+ const SkAlpha aa[]) const SK_OVERRIDE;
virtual void xfer16(uint16_t dst[], const SkPMColor src[], int count,
- const SkAlpha aa[]) SK_OVERRIDE;
+ const SkAlpha aa[]) const SK_OVERRIDE;
virtual void xfer4444(uint16_t dst[], const SkPMColor src[], int count,
- const SkAlpha aa[]) SK_OVERRIDE;
+ const SkAlpha aa[]) const SK_OVERRIDE;
virtual void xferA8(SkAlpha dst[], const SkPMColor src[], int count,
- const SkAlpha aa[]) SK_OVERRIDE;
+ const SkAlpha aa[]) const SK_OVERRIDE;
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkProcXfermode)
diff --git a/include/effects/Sk1DPathEffect.h b/include/effects/Sk1DPathEffect.h
index 6624b4be13..0ceadc1bb9 100644
--- a/include/effects/Sk1DPathEffect.h
+++ b/include/effects/Sk1DPathEffect.h
@@ -16,19 +16,20 @@ class SkPathMeasure;
// This class is not exported to java.
class SK_API Sk1DPathEffect : public SkPathEffect {
public:
- virtual bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*) SK_OVERRIDE;
+ virtual bool filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec*) const SK_OVERRIDE;
protected:
/** Called at the start of each contour, returns the initial offset
into that contour.
*/
- virtual SkScalar begin(SkScalar contourLength) = 0;
+ virtual SkScalar begin(SkScalar contourLength) const = 0;
/** Called with the current distance along the path, with the current matrix
for the point/tangent at the specified distance.
Return the distance to travel for the next call. If return <= 0, then that
contour is done.
*/
- virtual SkScalar next(SkPath* dst, SkScalar distance, SkPathMeasure&) = 0;
+ virtual SkScalar next(SkPath* dst, SkScalar dist, SkPathMeasure&) const = 0;
private:
typedef SkPathEffect INHERITED;
@@ -53,7 +54,8 @@ public:
*/
SkPath1DPathEffect(const SkPath& path, SkScalar advance, SkScalar phase, Style);
- virtual bool filterPath(SkPath*, const SkPath&, SkStrokeRec*) SK_OVERRIDE;
+ virtual bool filterPath(SkPath*, const SkPath&,
+ SkStrokeRec*) const SK_OVERRIDE;
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkPath1DPathEffect)
@@ -62,8 +64,8 @@ protected:
virtual void flatten(SkFlattenableWriteBuffer&) const SK_OVERRIDE;
// overrides from Sk1DPathEffect
- virtual SkScalar begin(SkScalar contourLength) SK_OVERRIDE;
- virtual SkScalar next(SkPath*, SkScalar distance, SkPathMeasure&) SK_OVERRIDE;
+ virtual SkScalar begin(SkScalar contourLength) const SK_OVERRIDE;
+ virtual SkScalar next(SkPath*, SkScalar, SkPathMeasure&) const SK_OVERRIDE;
private:
SkPath fPath; // copied from constructor
diff --git a/include/effects/Sk2DPathEffect.h b/include/effects/Sk2DPathEffect.h
index feb0da605c..3f774ae7b9 100644
--- a/include/effects/Sk2DPathEffect.h
+++ b/include/effects/Sk2DPathEffect.h
@@ -16,8 +16,8 @@ class SK_API Sk2DPathEffect : public SkPathEffect {
public:
Sk2DPathEffect(const SkMatrix& mat);
- // overrides
- virtual bool filterPath(SkPath*, const SkPath&, SkStrokeRec*) SK_OVERRIDE;
+ virtual bool filterPath(SkPath*, const SkPath&,
+ SkStrokeRec*) const SK_OVERRIDE;
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Sk2DPathEffect)
@@ -28,15 +28,15 @@ protected:
next() will receive u and v values within these bounds,
and then a call to end() will signal the end of processing.
*/
- virtual void begin(const SkIRect& uvBounds, SkPath* dst);
- virtual void next(const SkPoint& loc, int u, int v, SkPath* dst);
- virtual void end(SkPath* dst);
+ virtual void begin(const SkIRect& uvBounds, SkPath* dst) const;
+ virtual void next(const SkPoint& loc, int u, int v, SkPath* dst) const;
+ virtual void end(SkPath* dst) const;
/** Low-level virtual called per span of locations in the u-direction.
The default implementation calls next() repeatedly with each
location.
*/
- virtual void nextSpan(int u, int v, int ucount, SkPath* dst);
+ virtual void nextSpan(int u, int v, int ucount, SkPath* dst) const;
const SkMatrix& getMatrix() const { return fMatrix; }
@@ -61,12 +61,13 @@ public:
SkLine2DPathEffect(SkScalar width, const SkMatrix& matrix)
: Sk2DPathEffect(matrix), fWidth(width) {}
- virtual bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec* rec) SK_OVERRIDE;
+ virtual bool filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec*) const SK_OVERRIDE;
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLine2DPathEffect)
protected:
- virtual void nextSpan(int u, int v, int ucount, SkPath* dst) SK_OVERRIDE;
+ virtual void nextSpan(int u, int v, int ucount, SkPath*) const SK_OVERRIDE;
SkLine2DPathEffect(SkFlattenableReadBuffer&);
@@ -92,7 +93,7 @@ protected:
SkPath2DPathEffect(SkFlattenableReadBuffer& buffer);
virtual void flatten(SkFlattenableWriteBuffer&) const SK_OVERRIDE;
- virtual void next(const SkPoint&, int u, int v, SkPath* dst) SK_OVERRIDE;
+ virtual void next(const SkPoint&, int u, int v, SkPath*) const SK_OVERRIDE;
private:
SkPath fPath;
diff --git a/include/effects/SkAvoidXfermode.h b/include/effects/SkAvoidXfermode.h
index 1be26791bd..d5b33e3174 100644
--- a/include/effects/SkAvoidXfermode.h
+++ b/include/effects/SkAvoidXfermode.h
@@ -41,13 +41,13 @@ public:
// overrides from SkXfermode
virtual void xfer32(SkPMColor dst[], const SkPMColor src[], int count,
- const SkAlpha aa[]) SK_OVERRIDE;
+ const SkAlpha aa[]) const SK_OVERRIDE;
virtual void xfer16(uint16_t dst[], const SkPMColor src[], int count,
- const SkAlpha aa[]) SK_OVERRIDE;
+ const SkAlpha aa[]) const SK_OVERRIDE;
virtual void xfer4444(uint16_t dst[], const SkPMColor src[], int count,
- const SkAlpha aa[]) SK_OVERRIDE;
+ const SkAlpha aa[]) const SK_OVERRIDE;
virtual void xferA8(SkAlpha dst[], const SkPMColor src[], int count,
- const SkAlpha aa[]) SK_OVERRIDE;
+ const SkAlpha aa[]) const SK_OVERRIDE;
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkAvoidXfermode)
diff --git a/include/effects/SkBlurDrawLooper.h b/include/effects/SkBlurDrawLooper.h
index fa730e382c..c4107612cb 100644
--- a/include/effects/SkBlurDrawLooper.h
+++ b/include/effects/SkBlurDrawLooper.h
@@ -32,7 +32,7 @@ public:
kOverrideColor_BlurFlag = 0x02,
kHighQuality_BlurFlag = 0x04,
/** mask for all blur flags */
- kAll_BlurFlag = 0x07
+ kAll_BlurFlag = 0x07
};
SkBlurDrawLooper(SkScalar radius, SkScalar dx, SkScalar dy, SkColor color,
diff --git a/include/effects/SkColorFilterImageFilter.h b/include/effects/SkColorFilterImageFilter.h
index e9124f9123..bff9293816 100755
--- a/include/effects/SkColorFilterImageFilter.h
+++ b/include/effects/SkColorFilterImageFilter.h
@@ -14,7 +14,7 @@ class SkColorFilter;
class SK_API SkColorFilterImageFilter : public SkSingleInputImageFilter {
public:
- SkColorFilterImageFilter(SkColorFilter* cf, SkImageFilter* input = NULL);
+ static SkColorFilterImageFilter* Create(SkColorFilter* cf, SkImageFilter* input = NULL);
virtual ~SkColorFilterImageFilter();
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkColorFilterImageFilter)
@@ -29,6 +29,7 @@ protected:
virtual SkColorFilter* asColorFilter() const SK_OVERRIDE;
private:
+ SkColorFilterImageFilter(SkColorFilter* cf, SkImageFilter* input);
SkColorFilter* fColorFilter;
typedef SkSingleInputImageFilter INHERITED;
diff --git a/include/effects/SkColorMatrix.h b/include/effects/SkColorMatrix.h
index ff02d9ddc7..84a3b7c497 100644
--- a/include/effects/SkColorMatrix.h
+++ b/include/effects/SkColorMatrix.h
@@ -39,6 +39,12 @@ public:
void setSaturation(SkScalar sat);
void setRGB2YUV();
void setYUV2RGB();
+
+ bool operator==(const SkColorMatrix& other) const {
+ return 0 == memcmp(fMat, other.fMat, sizeof(fMat));
+ }
+
+ bool operator!=(const SkColorMatrix& other) const { return !((*this) == other); }
};
#endif
diff --git a/include/effects/SkColorMatrixFilter.h b/include/effects/SkColorMatrixFilter.h
index 005781f084..222646e032 100644
--- a/include/effects/SkColorMatrixFilter.h
+++ b/include/effects/SkColorMatrixFilter.h
@@ -17,15 +17,17 @@ public:
SkColorMatrixFilter(const SkScalar array[20]);
// overrides from SkColorFilter
- virtual void filterSpan(const SkPMColor src[], int count, SkPMColor[]) SK_OVERRIDE;
- virtual void filterSpan16(const uint16_t src[], int count, uint16_t[]) SK_OVERRIDE;
- virtual uint32_t getFlags() SK_OVERRIDE;
- virtual bool asColorMatrix(SkScalar matrix[20]) SK_OVERRIDE;
+ virtual void filterSpan(const SkPMColor src[], int count, SkPMColor[]) const SK_OVERRIDE;
+ virtual void filterSpan16(const uint16_t src[], int count, uint16_t[]) const SK_OVERRIDE;
+ virtual uint32_t getFlags() const SK_OVERRIDE;
+ virtual bool asColorMatrix(SkScalar matrix[20]) const SK_OVERRIDE;
+#if SK_SUPPORT_GPU
+ virtual GrEffect* asNewEffect(GrContext*) const SK_OVERRIDE;
+#endif
struct State {
int32_t fArray[20];
int fShift;
- int32_t fResult[4];
};
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkColorMatrixFilter)
@@ -37,8 +39,8 @@ protected:
private:
SkColorMatrix fMatrix;
- typedef void (*Proc)(State*, unsigned r, unsigned g, unsigned b,
- unsigned a);
+ typedef void (*Proc)(const State&, unsigned r, unsigned g, unsigned b,
+ unsigned a, int32_t result[4]);
Proc fProc;
State fState;
diff --git a/include/effects/SkCornerPathEffect.h b/include/effects/SkCornerPathEffect.h
index 88afea3a83..ee89219f85 100644
--- a/include/effects/SkCornerPathEffect.h
+++ b/include/effects/SkCornerPathEffect.h
@@ -23,9 +23,8 @@ public:
SkCornerPathEffect(SkScalar radius);
virtual ~SkCornerPathEffect();
- // overrides for SkPathEffect
- // This method is not exported to java.
- virtual bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*) SK_OVERRIDE;
+ virtual bool filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec*) const SK_OVERRIDE;
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkCornerPathEffect)
diff --git a/include/effects/SkDashPathEffect.h b/include/effects/SkDashPathEffect.h
index 03974663e5..cc9ad75321 100644
--- a/include/effects/SkDashPathEffect.h
+++ b/include/effects/SkDashPathEffect.h
@@ -36,14 +36,18 @@ public:
Note: only affects stroked paths.
*/
- SkDashPathEffect(const SkScalar intervals[], int count, SkScalar phase, bool scaleToFit = false);
+ SkDashPathEffect(const SkScalar intervals[], int count, SkScalar phase,
+ bool scaleToFit = false);
virtual ~SkDashPathEffect();
- virtual bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*) SK_OVERRIDE;
+ virtual bool filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec*) const SK_OVERRIDE;
+
+ virtual bool asPoints(PointData* results, const SkPath& src,
+ const SkStrokeRec&, const SkMatrix&) const SK_OVERRIDE;
+
+ virtual Factory getFactory() SK_OVERRIDE;
- // overrides for SkFlattenable
- // This method is not exported to java.
- virtual Factory getFactory();
static SkFlattenable* CreateProc(SkFlattenableReadBuffer&);
protected:
diff --git a/include/effects/SkDiscretePathEffect.h b/include/effects/SkDiscretePathEffect.h
index 67057485ed..029ebfd35c 100644
--- a/include/effects/SkDiscretePathEffect.h
+++ b/include/effects/SkDiscretePathEffect.h
@@ -22,7 +22,8 @@ public:
*/
SkDiscretePathEffect(SkScalar segLength, SkScalar deviation);
- virtual bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*) SK_OVERRIDE;
+ virtual bool filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec*) const SK_OVERRIDE;
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkDiscretePathEffect)
diff --git a/include/effects/SkEmbossMaskFilter.h b/include/effects/SkEmbossMaskFilter.h
index 96c25b247d..ce6447371f 100644
--- a/include/effects/SkEmbossMaskFilter.h
+++ b/include/effects/SkEmbossMaskFilter.h
@@ -27,10 +27,10 @@ public:
// overrides from SkMaskFilter
// This method is not exported to java.
- virtual SkMask::Format getFormat();
+ virtual SkMask::Format getFormat() const SK_OVERRIDE;
// This method is not exported to java.
virtual bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&,
- SkIPoint* margin);
+ SkIPoint* margin) const SK_OVERRIDE;
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkEmbossMaskFilter)
diff --git a/include/effects/SkKernel33MaskFilter.h b/include/effects/SkKernel33MaskFilter.h
index 41e73c91ec..9fcbb6afa7 100644
--- a/include/effects/SkKernel33MaskFilter.h
+++ b/include/effects/SkKernel33MaskFilter.h
@@ -15,11 +15,11 @@ public:
SkKernel33ProcMaskFilter(unsigned percent256 = 256)
: fPercent256(percent256) {}
- virtual uint8_t computeValue(uint8_t* const* srcRows) = 0;
+ virtual uint8_t computeValue(uint8_t* const* srcRows) const = 0;
- // overrides from SkMaskFilter
- virtual SkMask::Format getFormat();
- virtual bool filterMask(SkMask*, const SkMask&, const SkMatrix&, SkIPoint*);
+ virtual SkMask::Format getFormat() const SK_OVERRIDE;
+ virtual bool filterMask(SkMask*, const SkMask&, const SkMatrix&,
+ SkIPoint*) const SK_OVERRIDE;
protected:
SkKernel33ProcMaskFilter(SkFlattenableReadBuffer& rb);
@@ -42,7 +42,7 @@ public:
}
// override from SkKernel33ProcMaskFilter
- virtual uint8_t computeValue(uint8_t* const* srcRows);
+ virtual uint8_t computeValue(uint8_t* const* srcRows) const SK_OVERRIDE;
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkKernel33MaskFilter)
diff --git a/include/effects/SkLayerRasterizer.h b/include/effects/SkLayerRasterizer.h
index 7a1ef74ac2..65d1be0bc1 100644
--- a/include/effects/SkLayerRasterizer.h
+++ b/include/effects/SkLayerRasterizer.h
@@ -41,7 +41,7 @@ protected:
// override from SkRasterizer
virtual bool onRasterize(const SkPath& path, const SkMatrix& matrix,
const SkIRect* clipBounds,
- SkMask* mask, SkMask::CreateMode mode);
+ SkMask* mask, SkMask::CreateMode mode) const;
private:
SkDeque fLayers;
diff --git a/include/effects/SkMagnifierImageFilter.h b/include/effects/SkMagnifierImageFilter.h
index 144fdb42ca..400a010188 100644
--- a/include/effects/SkMagnifierImageFilter.h
+++ b/include/effects/SkMagnifierImageFilter.h
@@ -16,8 +16,8 @@ class SK_API SkMagnifierImageFilter : public SkImageFilter {
public:
SkMagnifierImageFilter(SkRect srcRect, SkScalar inset);
- virtual bool asNewCustomStage(GrCustomStage** stage,
- GrTexture* texture) const SK_OVERRIDE;
+ virtual bool asNewEffect(GrEffect** effect,
+ GrTexture* texture) const SK_OVERRIDE;
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkMagnifierImageFilter)
diff --git a/include/effects/SkMatrixConvolutionImageFilter.h b/include/effects/SkMatrixConvolutionImageFilter.h
index f7440cd9ec..f4d2c8e8cc 100644
--- a/include/effects/SkMatrixConvolutionImageFilter.h
+++ b/include/effects/SkMatrixConvolutionImageFilter.h
@@ -62,7 +62,7 @@ protected:
SkBitmap* result, SkIPoint* loc) SK_OVERRIDE;
#if SK_SUPPORT_GPU
- virtual bool asNewCustomStage(GrCustomStage** stage, GrTexture*) const SK_OVERRIDE;
+ virtual bool asNewEffect(GrEffect**, GrTexture*) const SK_OVERRIDE;
#endif
private:
diff --git a/include/effects/SkMergeImageFilter.h b/include/effects/SkMergeImageFilter.h
new file mode 100755
index 0000000000..8c4313dd13
--- /dev/null
+++ b/include/effects/SkMergeImageFilter.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkMergeImageFilter_DEFINED
+#define SkMergeImageFilter_DEFINED
+
+#include "SkImageFilter.h"
+
+#include "SkXfermode.h"
+
+class SK_API SkMergeImageFilter : public SkImageFilter {
+public:
+ SkMergeImageFilter(SkImageFilter* first, SkImageFilter* second,
+ SkXfermode::Mode = SkXfermode::kSrcOver_Mode);
+ SkMergeImageFilter(SkImageFilter* filters[], int count,
+ const SkXfermode::Mode modes[] = NULL);
+ virtual ~SkMergeImageFilter();
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkMergeImageFilter)
+
+protected:
+ SkMergeImageFilter(SkFlattenableReadBuffer& buffer);
+ virtual void flatten(SkFlattenableWriteBuffer&) const SK_OVERRIDE;
+
+ virtual bool onFilterImage(Proxy*, const SkBitmap& src, const SkMatrix&,
+ SkBitmap* result, SkIPoint* loc) SK_OVERRIDE;
+ virtual bool onFilterBounds(const SkIRect&, const SkMatrix&, SkIRect*) SK_OVERRIDE;
+
+private:
+ uint8_t* fModes; // SkXfermode::Mode
+
+ // private storage, to avoid dynamically allocating storage for our copy
+ // of the modes (unless the count is so large we can't fit).
+ intptr_t fStorage[16];
+
+ void initAllocModes();
+ void initModes(const SkXfermode::Mode []);
+
+ typedef SkImageFilter INHERITED;
+};
+
+#endif
diff --git a/include/effects/SkOffsetImageFilter.h b/include/effects/SkOffsetImageFilter.h
new file mode 100644
index 0000000000..74d6ebc191
--- /dev/null
+++ b/include/effects/SkOffsetImageFilter.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkOffsetImageFilter_DEFINED
+#define SkOffsetImageFilter_DEFINED
+
+#include "SkSingleInputImageFilter.h"
+#include "SkPoint.h"
+
+class SK_API SkOffsetImageFilter : public SkSingleInputImageFilter {
+public:
+ SkOffsetImageFilter(SkScalar dx, SkScalar dy, SkImageFilter* input = NULL);
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkOffsetImageFilter)
+
+protected:
+ SkOffsetImageFilter(SkFlattenableReadBuffer& buffer);
+ virtual void flatten(SkFlattenableWriteBuffer&) const SK_OVERRIDE;
+
+ virtual bool onFilterImage(Proxy*, const SkBitmap& src, const SkMatrix&,
+ SkBitmap* result, SkIPoint* loc) SK_OVERRIDE;
+ virtual bool onFilterBounds(const SkIRect&, const SkMatrix&, SkIRect*) SK_OVERRIDE;
+
+private:
+ SkVector fOffset;
+
+ typedef SkSingleInputImageFilter INHERITED;
+};
+
+#endif
diff --git a/include/effects/SkPaintFlagsDrawFilter.h b/include/effects/SkPaintFlagsDrawFilter.h
index fbf1807708..cb2a8b7e95 100644
--- a/include/effects/SkPaintFlagsDrawFilter.h
+++ b/include/effects/SkPaintFlagsDrawFilter.h
@@ -14,8 +14,7 @@ class SK_API SkPaintFlagsDrawFilter : public SkDrawFilter {
public:
SkPaintFlagsDrawFilter(uint32_t clearFlags, uint32_t setFlags);
- // overrides
- virtual void filter(SkPaint*, Type);
+ virtual bool filter(SkPaint*, Type) SK_OVERRIDE;
private:
uint16_t fClearFlags; // user specified
diff --git a/include/effects/SkStippleMaskFilter.h b/include/effects/SkStippleMaskFilter.h
index b5d162e005..be03c4590e 100644
--- a/include/effects/SkStippleMaskFilter.h
+++ b/include/effects/SkStippleMaskFilter.h
@@ -20,10 +20,10 @@ public:
virtual bool filterMask(SkMask* dst, const SkMask& src,
const SkMatrix& matrix,
- SkIPoint* margin) SK_OVERRIDE;
+ SkIPoint* margin) const SK_OVERRIDE;
// getFormat is from SkMaskFilter
- virtual SkMask::Format getFormat() SK_OVERRIDE {
+ virtual SkMask::Format getFormat() const SK_OVERRIDE {
return SkMask::kA8_Format;
}
diff --git a/include/effects/SkTableMaskFilter.h b/include/effects/SkTableMaskFilter.h
index c407b40b94..feb3b13847 100644
--- a/include/effects/SkTableMaskFilter.h
+++ b/include/effects/SkTableMaskFilter.h
@@ -22,8 +22,6 @@ public:
SkTableMaskFilter(const uint8_t table[256]);
virtual ~SkTableMaskFilter();
- void setTable(const uint8_t table[256]);
-
/** Utility that sets the gamma table
*/
static void MakeGammaTable(uint8_t table[256], SkScalar gamma);
@@ -45,9 +43,9 @@ public:
return SkNEW_ARGS(SkTableMaskFilter, (table));
}
- // overrides from SkMaskFilter
- virtual SkMask::Format getFormat();
- virtual bool filterMask(SkMask*, const SkMask&, const SkMatrix&, SkIPoint*);
+ virtual SkMask::Format getFormat() const SK_OVERRIDE;
+ virtual bool filterMask(SkMask*, const SkMask&, const SkMatrix&,
+ SkIPoint*) const SK_OVERRIDE;
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkTableMaskFilter)
diff --git a/include/effects/SkTestImageFilters.h b/include/effects/SkTestImageFilters.h
index 5174856dc1..c38c03793b 100755
--- a/include/effects/SkTestImageFilters.h
+++ b/include/effects/SkTestImageFilters.h
@@ -4,31 +4,9 @@
#include "SkImageFilter.h"
#include "SkPoint.h"
-class SK_API SkOffsetImageFilter : public SkImageFilter {
-public:
- SkOffsetImageFilter(SkScalar dx, SkScalar dy) : INHERITED(0) {
- fOffset.set(dx, dy);
- }
-
- SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkOffsetImageFilter)
-
-protected:
- SkOffsetImageFilter(SkFlattenableReadBuffer& buffer);
- virtual void flatten(SkFlattenableWriteBuffer&) const SK_OVERRIDE;
-
- virtual bool onFilterImage(Proxy*, const SkBitmap& src, const SkMatrix&,
- SkBitmap* result, SkIPoint* loc) SK_OVERRIDE;
- virtual bool onFilterBounds(const SkIRect&, const SkMatrix&, SkIRect*) SK_OVERRIDE;
-
-private:
- SkVector fOffset;
-
- typedef SkImageFilter INHERITED;
-};
-
class SK_API SkComposeImageFilter : public SkImageFilter {
public:
- SkComposeImageFilter(SkImageFilter* outer, SkImageFilter* inner) : INHERITED(2, outer, inner) {}
+ SkComposeImageFilter(SkImageFilter* outer, SkImageFilter* inner) : INHERITED(outer, inner) {}
virtual ~SkComposeImageFilter();
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkComposeImageFilter)
@@ -44,40 +22,6 @@ private:
typedef SkImageFilter INHERITED;
};
-#include "SkXfermode.h"
-
-class SK_API SkMergeImageFilter : public SkImageFilter {
-public:
- SkMergeImageFilter(SkImageFilter* first, SkImageFilter* second,
- SkXfermode::Mode = SkXfermode::kSrcOver_Mode);
- SkMergeImageFilter(SkImageFilter* const filters[], int count,
- const SkXfermode::Mode modes[] = NULL);
- virtual ~SkMergeImageFilter();
-
- SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkMergeImageFilter)
-
-protected:
- SkMergeImageFilter(SkFlattenableReadBuffer& buffer);
- virtual void flatten(SkFlattenableWriteBuffer&) const SK_OVERRIDE;
-
- virtual bool onFilterImage(Proxy*, const SkBitmap& src, const SkMatrix&,
- SkBitmap* result, SkIPoint* loc) SK_OVERRIDE;
- virtual bool onFilterBounds(const SkIRect&, const SkMatrix&, SkIRect*) SK_OVERRIDE;
-
-private:
- uint8_t* fModes; // SkXfermode::Mode
- int fCount;
-
- // private storage, to avoid dynamically allocating storage for our copy
- // of the filters and modes (unless fCount is so large we can't fit).
- intptr_t fStorage[16];
-
- void initAllocModes();
- void initModes(const SkXfermode::Mode []);
-
- typedef SkImageFilter INHERITED;
-};
-
///////////////////////////////////////////////////////////////////////////////
// Fun mode that scales down (only) and then scales back up to look pixelated
diff --git a/include/gpu/GrAARectRenderer.h b/include/gpu/GrAARectRenderer.h
index e8b9f158bc..0cf7faac05 100644
--- a/include/gpu/GrAARectRenderer.h
+++ b/include/gpu/GrAARectRenderer.h
@@ -54,10 +54,6 @@ private:
GrIndexBuffer* fAAFillRectIndexBuffer;
GrIndexBuffer* fAAStrokeRectIndexBuffer;
- static const uint16_t gFillAARectIdx[];
- static const uint16_t gStrokeAARectIdx[];
-
- static int aaFillRectIndexCount();
GrIndexBuffer* aaFillRectIndexBuffer(GrGpu* gpu);
static int aaStrokeRectIndexCount();
diff --git a/include/gpu/GrBackendEffectFactory.h b/include/gpu/GrBackendEffectFactory.h
new file mode 100644
index 0000000000..2bfefb93ee
--- /dev/null
+++ b/include/gpu/GrBackendEffectFactory.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrBackendEffectFactory_DEFINED
+#define GrBackendEffectFactory_DEFINED
+
+#include "GrTypes.h"
+#include "SkTemplates.h"
+#include "SkThread_platform.h"
+#include "GrNoncopyable.h"
+
+/** Given a GrEffect of a particular type, creates the corresponding graphics-backend-specific
+ effect object. Also tracks equivalence of shaders generated via a key. Each factory instance
+ is assigned a generation ID at construction. The ID of the return of GrEffect::getFactory()
+ is used as a type identifier. Thus a GrEffect subclass must return a singleton from
+ getFactory(). GrEffect subclasses should use the derived class GrTBackendEffectFactory that is
+ templated on the GrEffect subclass as their factory object. It requires that the GrEffect
+ subclass has a nested class (or typedef) GLEffect which is its GL implementation and a subclass
+ of GrGLEffect.
+ */
+
+class GrEffect;
+class GrEffectStage;
+class GrGLEffect;
+class GrGLCaps;
+
+class GrBackendEffectFactory : public GrNoncopyable {
+public:
+ typedef uint32_t EffectKey;
+ enum {
+ kNoEffectKey = 0,
+ kEffectKeyBits = 12,
+ /**
+ * Some aspects of the generated code may be determined by the particular textures that are
+ * associated with the effect. These manipulations are performed by GrGLShaderBuilder beyond
+ * GrGLEffects' control. So there is a dedicated part of the key which is combined
+ * automatically with the bits produced by GrGLEffect::GenKey().
+ */
+ kTextureKeyBits = 6
+ };
+
+ virtual EffectKey glEffectKey(const GrEffectStage&, const GrGLCaps&) const = 0;
+ virtual GrGLEffect* createGLInstance(const GrEffect&) const = 0;
+
+ bool operator ==(const GrBackendEffectFactory& b) const {
+ return fEffectClassID == b.fEffectClassID;
+ }
+ bool operator !=(const GrBackendEffectFactory& b) const {
+ return !(*this == b);
+ }
+
+ virtual const char* name() const = 0;
+
+protected:
+ enum {
+ kIllegalEffectClassID = 0,
+ };
+
+ GrBackendEffectFactory() {
+ fEffectClassID = kIllegalEffectClassID;
+ }
+
+ static EffectKey GenID() {
+ GR_DEBUGCODE(static const int32_t kClassIDBits = 8 * sizeof(EffectKey) -
+ kTextureKeyBits -
+ kEffectKeyBits);
+ // fCurrEffectClassID has been initialized to kIllegalEffectClassID. The
+ // atomic inc returns the old value not the incremented value. So we add
+ // 1 to the returned value.
+ int32_t id = sk_atomic_inc(&fCurrEffectClassID) + 1;
+ GrAssert(id < (1 << kClassIDBits));
+ return static_cast<EffectKey>(id);
+ }
+
+ EffectKey fEffectClassID;
+
+private:
+ static int32_t fCurrEffectClassID;
+};
+
+#endif
diff --git a/include/gpu/GrConfig.h b/include/gpu/GrConfig.h
index c61c34e193..766212de36 100644
--- a/include/gpu/GrConfig.h
+++ b/include/gpu/GrConfig.h
@@ -126,8 +126,12 @@ typedef unsigned __int64 uint64_t;
* macros here before anyone else has a chance to include stdint.h without
* these.
*/
+#ifndef __STDC_LIMIT_MACROS
#define __STDC_LIMIT_MACROS
+#endif
+#ifndef __STDC_CONSTANT_MACROS
#define __STDC_CONSTANT_MACROS
+#endif
#include <stdint.h>
#endif
@@ -139,7 +143,7 @@ typedef unsigned __int64 uint64_t;
* GR_USER_CONFIG_FILE. It should be defined relative to GrConfig.h
*
* e.g. it can specify GR_DEBUG/GR_RELEASE as it please, change the BUILD
- * target, or supply its own defines for anything else (e.g. GR_SCALAR)
+ * target, or supply its own defines for anything else (e.g. GR_DEFAULT_TEXTURE_CACHE_MB_LIMIT)
*/
#if !defined(GR_USER_CONFIG_FILE)
#include "GrUserConfig.h"
@@ -311,13 +315,6 @@ inline void GrCrash(const char* msg) { GrPrintf(msg); GrAlwaysAssert(false); }
#endif
#endif
-#if !defined(GR_SCALAR_IS_FLOAT)
- #define GR_SCALAR_IS_FLOAT 0
-#endif
-#if !defined(GR_SCALAR_IS_FIXED)
- #define GR_SCALAR_IS_FIXED 0
-#endif
-
#if !defined(GR_TEXT_SCALAR_TYPE_IS_USHORT)
#define GR_TEXT_SCALAR_TYPE_IS_USHORT 0
#endif
@@ -369,6 +366,24 @@ inline void GrCrash(const char* msg) { GrPrintf(msg); GrAlwaysAssert(false); }
#define GR_GEOM_BUFFER_LOCK_THRESHOLD (1 << 15)
#endif
+/**
+ * GR_DEFAULT_TEXTURE_CACHE_MB_LIMIT gives a threshold (in megabytes) for the
+ * maximum size of the texture cache in vram. The value is only a default and
+ * can be overridden at runtime.
+ */
+#if !defined(GR_DEFAULT_TEXTURE_CACHE_MB_LIMIT)
+ #define GR_DEFAULT_TEXTURE_CACHE_MB_LIMIT 96
+#endif
+
+/**
+ * GR_USE_NEW_GL_SHADER_SOURCE_SIGNATURE is for compatibility with the new version
+ * of the OpenGLES2.0 headers from Khronos. glShaderSource now takes a const char * const *,
+ * instead of a const char **.
+ */
+#if !defined(GR_USE_NEW_GL_SHADER_SOURCE_SIGNATURE)
+ #define GR_USE_NEW_GL_SHADER_SOURCE_SIGNATURE 0
+#endif
+
///////////////////////////////////////////////////////////////////////////////
// tail section:
//
@@ -388,12 +403,6 @@ inline void GrCrash(const char* msg) { GrPrintf(msg); GrAlwaysAssert(false); }
#endif
-#if !GR_SCALAR_IS_FLOAT && !GR_SCALAR_IS_FIXED
- #undef GR_SCALAR_IS_FLOAT
- #define GR_SCALAR_IS_FLOAT 1
- #pragma message GR_WARN("Scalar type not defined, defaulting to float")
-#endif
-
#if !GR_TEXT_SCALAR_IS_FLOAT && \
!GR_TEXT_SCALAR_IS_FIXED && \
!GR_TEXT_SCALAR_IS_USHORT
diff --git a/include/gpu/GrContext.h b/include/gpu/GrContext.h
index be0f573f14..06d26829d3 100644
--- a/include/gpu/GrContext.h
+++ b/include/gpu/GrContext.h
@@ -10,42 +10,45 @@
#ifndef GrContext_DEFINED
#define GrContext_DEFINED
-#include "GrConfig.h"
-#include "GrPaint.h"
+#include "GrColor.h"
#include "GrAARectRenderer.h"
#include "GrClipData.h"
+#include "SkMatrix.h"
+#include "GrPaint.h"
+#include "GrPathRendererChain.h"
// not strictly needed but requires WK change in LayerTextureUpdaterCanvas to
// remove.
#include "GrRenderTarget.h"
-#include "SkClipStack.h"
+#include "GrRefCnt.h"
+#include "GrTexture.h"
class GrAutoScratchTexture;
-class GrCacheKey;
class GrDrawState;
class GrDrawTarget;
+class GrEffect;
class GrFontCache;
class GrGpu;
class GrIndexBuffer;
class GrIndexBufferAllocPool;
class GrInOrderDrawBuffer;
class GrPathRenderer;
-class GrPathRendererChain;
class GrResourceEntry;
class GrResourceCache;
class GrStencilBuffer;
+class GrTextureParams;
class GrVertexBuffer;
class GrVertexBufferAllocPool;
class GrSoftwarePathRenderer;
+class SkStrokeRec;
class GR_API GrContext : public GrRefCnt {
public:
SK_DECLARE_INST_COUNT(GrContext)
/**
- * Creates a GrContext from within a 3D context.
+ * Creates a GrContext for a backend context.
*/
- static GrContext* Create(GrEngine engine,
- GrPlatform3DContext context3D);
+ static GrContext* Create(GrBackend, GrBackendContext);
/**
* Returns the number of GrContext instances for the current thread.
@@ -134,12 +137,6 @@ public:
void* srcData, size_t rowBytes);
/**
- * Look for a texture that matches 'key' in the cache. If not found,
- * return NULL.
- */
- GrTexture* findTexture(const GrCacheKey& key);
-
- /**
* Search for an entry based on key and dimensions. If found,
* return it. The return value will be NULL if not found.
*
@@ -286,7 +283,7 @@ public:
bool isConfigRenderable(GrPixelConfig config) const;
///////////////////////////////////////////////////////////////////////////
- // Platform Surfaces
+ // Backend Surfaces
/**
* Wraps an existing texture with a GrTexture object.
@@ -298,11 +295,11 @@ public:
*
* @return GrTexture object or NULL on failure.
*/
- GrTexture* createPlatformTexture(const GrPlatformTextureDesc& desc);
+ GrTexture* wrapBackendTexture(const GrBackendTextureDesc& desc);
/**
* Wraps an existing render target with a GrRenderTarget object. It is
- * similar to createPlatformTexture but can be used to draw into surfaces
+ * similar to wrapBackendTexture but can be used to draw into surfaces
* that are not also textures (e.g. FBO 0 in OpenGL, or an MSAA buffer that
* the client will resolve to a texture).
*
@@ -310,8 +307,7 @@ public:
*
* @return GrTexture object or NULL on failure.
*/
- GrRenderTarget* createPlatformRenderTarget(
- const GrPlatformRenderTargetDesc& desc);
+ GrRenderTarget* wrapBackendRenderTarget(const GrBackendRenderTargetDesc& desc);
///////////////////////////////////////////////////////////////////////////
// Matrix state
@@ -320,13 +316,13 @@ public:
* Gets the current transformation matrix.
* @return the current matrix.
*/
- const GrMatrix& getMatrix() const;
+ const SkMatrix& getMatrix() const;
/**
* Sets the transformation matrix.
* @param m the matrix to set.
*/
- void setMatrix(const GrMatrix& m);
+ void setMatrix(const SkMatrix& m);
/**
* Sets the current transformation matrix to identity.
@@ -338,7 +334,7 @@ public:
* current matrix.
* @param m the matrix to concat.
*/
- void concatMatrix(const GrMatrix& m) const;
+ void concatMatrix(const SkMatrix& m) const;
///////////////////////////////////////////////////////////////////////////
@@ -386,8 +382,8 @@ public:
*/
void drawRect(const GrPaint& paint,
const GrRect&,
- GrScalar strokeWidth = -1,
- const GrMatrix* matrix = NULL);
+ SkScalar strokeWidth = -1,
+ const SkMatrix* matrix = NULL);
/**
* Maps a rect of paint coordinates onto the a rect of destination
@@ -407,19 +403,17 @@ public:
void drawRectToRect(const GrPaint& paint,
const GrRect& dstRect,
const GrRect& srcRect,
- const GrMatrix* dstMatrix = NULL,
- const GrMatrix* srcMatrix = NULL);
+ const SkMatrix* dstMatrix = NULL,
+ const SkMatrix* srcMatrix = NULL);
/**
* Draws a path.
*
* @param paint describes how to color pixels.
* @param path the path to draw
- * @param fill the path filling rule to use.
- * @param translate optional additional translation applied to the
- * path.
+ * @param stroke the stroke information (width, join, cap)
*/
- void drawPath(const GrPaint& paint, const SkPath& path, GrPathFill fill);
+ void drawPath(const GrPaint& paint, const SkPath& path, const SkStrokeRec& stroke);
/**
* Draws vertices with a paint.
@@ -590,11 +584,15 @@ public:
/**
- * Copies all texels from one texture to another.
+ * Copies a rectangle of texels from src to dst. The size of dst is the size of the rectangle
+ * copied and topLeft is the position of the rect in src. The rectangle is clipped to src's
+ * bounds.
* @param src the texture to copy from.
* @param dst the render target to copy to.
+ * @param topLeft the point in src that will be copied to the top-left of dst. If NULL,
+ * (0, 0) will be used.
*/
- void copyTexture(GrTexture* src, GrRenderTarget* dst);
+ void copyTexture(GrTexture* src, GrRenderTarget* dst, const SkIPoint* topLeft = NULL);
/**
* Resolves a render target that has MSAA. The intermediate MSAA buffer is
@@ -603,7 +601,7 @@ public:
* be executed before the resolve.
*
* This is only necessary when a client wants to access the object directly
- * using the underlying graphics API. GrContext will detect when it must
+ * using the backend API directly. GrContext will detect when it must
* perform a resolve to a GrTexture used as the source of a draw or before
* reading pixels back from a GrTexture or GrRenderTarget.
*/
@@ -672,10 +670,10 @@ public:
* Save/restore the view-matrix in the context. It can optionally adjust a paint to account
* for a coordinate system change. Here is an example of how the paint param can be used:
*
- * A GrPaint is setup with custom stages. The stages will have access to the pre-matrix source
+ * A GrPaint is setup with GrEffects. The stages will have access to the pre-matrix source
* geometry positions when the draw is executed. Later on a decision is made to transform the
- * geometry to device space on the CPU. The custom stages now need to know that the space in
- * which the geometry will be specified has changed.
+ * geometry to device space on the CPU. The effects now need to know that the space in which
+ * the geometry will be specified has changed.
*
* Note that when restore is called (or in the destructor) the context's matrix will be
* restored. However, the paint will not be restored. The caller must make a copy of the
@@ -691,7 +689,7 @@ public:
/**
* Initializes by pre-concat'ing the context's current matrix with the preConcat param.
*/
- void setPreConcat(GrContext* context, const GrMatrix& preConcat, GrPaint* paint = NULL) {
+ void setPreConcat(GrContext* context, const SkMatrix& preConcat, GrPaint* paint = NULL) {
GrAssert(NULL != context);
this->restore();
@@ -711,7 +709,7 @@ public:
this->restore();
if (NULL != paint) {
- if (!paint->preConcatSamplerMatricesWithInverse(context->getMatrix())) {
+ if (!paint->sourceCoordChangeByInverse(context->getMatrix())) {
return false;
}
}
@@ -725,7 +723,7 @@ public:
* Replaces the context's matrix with a new matrix. Returns false if the inverse matrix is
* required to update a paint but the matrix cannot be inverted.
*/
- bool set(GrContext* context, const GrMatrix& newMatrix, GrPaint* paint = NULL) {
+ bool set(GrContext* context, const SkMatrix& newMatrix, GrPaint* paint = NULL) {
if (NULL != paint) {
if (!this->setIdentity(context, paint)) {
return false;
@@ -747,9 +745,9 @@ public:
* made, not the matrix at the time AutoMatrix was first initialized. In other words, this
* performs an incremental update of the paint.
*/
- void preConcat(const GrMatrix& preConcat, GrPaint* paint = NULL) {
+ void preConcat(const SkMatrix& preConcat, GrPaint* paint = NULL) {
if (NULL != paint) {
- paint->preConcatSamplerMatrices(preConcat);
+ paint->sourceCoordChange(preConcat);
}
fContext->concatMatrix(preConcat);
}
@@ -772,7 +770,7 @@ public:
private:
GrContext* fContext;
- GrMatrix fMatrix;
+ SkMatrix fMatrix;
};
class AutoClip : GrNoncopyable {
@@ -846,16 +844,28 @@ public:
void addStencilBuffer(GrStencilBuffer* sb);
GrStencilBuffer* findStencilBuffer(int width, int height, int sampleCnt);
- GrPathRenderer* getPathRenderer(const SkPath& path,
- GrPathFill fill,
- const GrDrawTarget* target,
- bool antiAlias,
- bool allowSW);
+ GrPathRenderer* getPathRenderer(
+ const SkPath& path,
+ const SkStrokeRec& stroke,
+ const GrDrawTarget* target,
+ bool allowSW,
+ GrPathRendererChain::DrawType drawType = GrPathRendererChain::kColor_DrawType,
+ GrPathRendererChain::StencilSupport* stencilSupport = NULL);
#if GR_CACHE_STATS
void printCacheStats() const;
#endif
+ ///////////////////////////////////////////////////////////////////////////
+ // Legacy names that will be kept until WebKit can be updated.
+ GrTexture* createPlatformTexture(const GrPlatformTextureDesc& desc) {
+ return this->wrapBackendTexture(desc);
+ }
+
+ GrRenderTarget* createPlatformRenderTarget(const GrPlatformRenderTargetDesc& desc) {
+ return wrapBackendRenderTarget(desc);
+ }
+
private:
// Used to indicate whether a draw should be performed immediately or queued in fDrawBuffer.
enum BufferedDraw {
@@ -900,7 +910,7 @@ private:
/// draw state is left unmodified.
GrDrawTarget* prepareToDraw(const GrPaint*, BufferedDraw);
- void internalDrawPath(const GrPaint& paint, const SkPath& path, GrPathFill fill);
+ void internalDrawPath(const GrPaint& paint, const SkPath& path, const SkStrokeRec& stroke);
GrTexture* createResizedTexture(const GrTextureDesc& desc,
const GrCacheData& cacheData,
@@ -916,8 +926,14 @@ private:
// for use with textures released from an GrAutoScratchTexture.
void addExistingTextureToCache(GrTexture* texture);
- GrCustomStage* createPMToUPMEffect(GrTexture* texture, bool swapRAndB);
- GrCustomStage* createUPMToPMEffect(GrTexture* texture, bool swapRAndB);
+ bool installPMToUPMEffect(GrTexture* texture,
+ bool swapRAndB,
+ const SkMatrix& matrix,
+ GrEffectStage* stage);
+ bool installUPMToPMEffect(GrTexture* texture,
+ bool swapRAndB,
+ const SkMatrix& matrix,
+ GrEffectStage* stage);
typedef GrRefCnt INHERITED;
};
diff --git a/include/gpu/GrContextFactory.h b/include/gpu/GrContextFactory.h
index 600bedae66..95d02a2f11 100644
--- a/include/gpu/GrContextFactory.h
+++ b/include/gpu/GrContextFactory.h
@@ -95,9 +95,8 @@ public:
if (!glCtx.get()->init(kBogusSize, kBogusSize)) {
return NULL;
}
- GrPlatform3DContext p3dctx =
- reinterpret_cast<GrPlatform3DContext>(glCtx.get()->gl());
- grCtx.reset(GrContext::Create(kOpenGL_Shaders_GrEngine, p3dctx));
+ GrBackendContext p3dctx = reinterpret_cast<GrBackendContext>(glCtx.get()->gl());
+ grCtx.reset(GrContext::Create(kOpenGL_GrBackend, p3dctx));
if (!grCtx.get()) {
return NULL;
}
diff --git a/include/gpu/GrCustomStageUnitTest.h b/include/gpu/GrCustomStageUnitTest.h
deleted file mode 100644
index e0f179db63..0000000000
--- a/include/gpu/GrCustomStageUnitTest.h
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright 2012 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef GrCustomStageUnitTest_DEFINED
-#define GrCustomStageUnitTest_DEFINED
-
-#include "SkRandom.h"
-#include "GrNoncopyable.h"
-#include "SkTArray.h"
-
-namespace GrCustomStageUnitTest {
-// Used to access the dummy textures in TestCreate procs.
-enum {
- kSkiaPMTextureIdx = 0,
- kAlphaTextureIdx = 1,
-};
-}
-
-#if SK_ALLOW_STATIC_GLOBAL_INITIALIZERS
-
-class GrCustomStage;
-class GrContext;
-class GrTexture;
-
-class GrCustomStageTestFactory : GrNoncopyable {
-public:
-
- typedef GrCustomStage* (*CreateProc)(SkRandom*, GrContext*, GrTexture* dummyTextures[]);
-
- GrCustomStageTestFactory(CreateProc createProc) {
- fCreateProc = createProc;
- GetFactories()->push_back(this);
- }
-
- static GrCustomStage* CreateStage(SkRandom* random,
- GrContext* context,
- GrTexture* dummyTextures[]) {
- uint32_t idx = random->nextRangeU(0, GetFactories()->count() - 1);
- GrCustomStageTestFactory* factory = (*GetFactories())[idx];
- return factory->fCreateProc(random, context, dummyTextures);
- }
-
-private:
- CreateProc fCreateProc;
- static SkTArray<GrCustomStageTestFactory*, true>* GetFactories();
-};
-
-/** GrCustomStage subclasses should insert this macro in their declaration to be included in the
- * program generation unit test.
- */
-#define GR_DECLARE_CUSTOM_STAGE_TEST \
- static GrCustomStageTestFactory gTestFactory; \
- static GrCustomStage* TestCreate(SkRandom*, GrContext*, GrTexture* dummyTextures[2])
-
-/** GrCustomStage subclasses should insert this macro in their implemenation file. They must then
- * also implement this static function:
- * GrCustomStage* CreateStage(SkRandom*, GrContext*, GrTexture* dummyTextures[2]);
- * dummyTextures[] are valied textures that they can optionally use for their texture accesses. The
- * first texture has config kSkia8888_PM_GrPixelConfig and the second has kAlpha_8_GrPixelConfig.
- * TestCreate functions are also free to create additional textures using the GrContext.
- */
-#define GR_DEFINE_CUSTOM_STAGE_TEST(CustomStage) \
- GrCustomStageTestFactory CustomStage :: gTestFactory(CustomStage :: TestCreate)
-
-#else // !SK_ALLOW_STATIC_GLOBAL_INITIALIZERS
-
-// The unit test relies on static initializers. Just declare the TestCreate function so that
-// its definitions will compile.
-#define GR_DECLARE_CUSTOM_STAGE_TEST \
- static GrCustomStage* TestCreate(SkRandom*, GrContext*, GrTexture* dummyTextures[2])
-#define GR_DEFINE_CUSTOM_STAGE_TEST(X)
-
-#endif // !SK_ALLOW_STATIC_GLOBAL_INITIALIZERS
-#endif
diff --git a/include/gpu/GrCustomStage.h b/include/gpu/GrEffect.h
index d75675c6c1..0adc00be00 100644
--- a/include/gpu/GrCustomStage.h
+++ b/include/gpu/GrEffect.h
@@ -5,15 +5,15 @@
* found in the LICENSE file.
*/
-#ifndef GrCustomStage_DEFINED
-#define GrCustomStage_DEFINED
+#ifndef GrEffect_DEFINED
+#define GrEffect_DEFINED
#include "GrRefCnt.h"
#include "GrNoncopyable.h"
-#include "GrProgramStageFactory.h"
-#include "GrCustomStageUnitTest.h"
+#include "GrEffectUnitTest.h"
#include "GrTextureAccess.h"
+class GrBackendEffectFactory;
class GrContext;
class GrTexture;
class SkString;
@@ -22,64 +22,60 @@ class SkString;
particular stage of the Ganesh shading pipeline.
Subclasses must have a function that produces a human-readable name:
static const char* Name();
- GrCustomStage objects *must* be immutable: after being constructed,
+ GrEffect objects *must* be immutable: after being constructed,
their fields may not change. (Immutability isn't actually required
until they've been used in a draw call, but supporting that would require
setters and getters that could fail, copy-on-write, or deep copying of these
- objects when they're stored by a GrGLProgramStage.)
+ objects when they're stored by a GrGLEffect.)
*/
-class GrCustomStage : public GrRefCnt {
+class GrEffect : public GrRefCnt {
public:
- SK_DECLARE_INST_COUNT(GrCustomStage)
+ SK_DECLARE_INST_COUNT(GrEffect)
- typedef GrProgramStageFactory::StageKey StageKey;
-
- explicit GrCustomStage(int numTextures);
- virtual ~GrCustomStage();
+ explicit GrEffect(int numTextures);
+ virtual ~GrEffect();
/** If given an input texture that is/is not opaque, is this
- stage guaranteed to produce an opaque output? */
+ effect guaranteed to produce an opaque output? */
virtual bool isOpaque(bool inputTextureIsOpaque) const;
- /** This object, besides creating back-end-specific helper
- objects, is used for run-time-type-identification. The factory should be
- an instance of templated class, GrTProgramStageFactory. It is templated
- on the subclass of GrCustomStage. The subclass must have a nested type
- (or typedef) named GLProgramStage which will be the subclass of
- GrGLProgramStage created by the factory.
+ /** This object, besides creating back-end-specific helper objects, is used for run-time-type-
+ identification. The factory should be an instance of templated class,
+ GrTBackendEffectFactory. It is templated on the subclass of GrEffect. The subclass must have
+ a nested type (or typedef) named GLEffect which will be the subclass of GrGLEffect created
+ by the factory.
Example:
- class MyCustomStage : public GrCustomStage {
+ class MyCustomEffect : public GrEffect {
...
- virtual const GrProgramStageFactory& getFactory() const
- SK_OVERRIDE {
- return GrTProgramStageFactory<MyCustomStage>::getInstance();
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<MyCustomEffect>::getInstance();
}
...
};
*/
- virtual const GrProgramStageFactory& getFactory() const = 0;
+ virtual const GrBackendEffectFactory& getFactory() const = 0;
- /** Returns true if the other custom stage will generate identical output.
+ /** Returns true if the other effect will generate identical output.
Must only be called if the two are already known to be of the
same type (i.e. they return the same value from getFactory()).
Equality is not the same thing as equivalence.
To test for equivalence (that they will generate the same
shader code, but may have different uniforms), check equality
- of the stageKey produced by the GrProgramStageFactory:
- a.getFactory().glStageKey(a) == b.getFactory().glStageKey(b).
+ of the EffectKey produced by the GrBackendEffectFactory:
+ a.getFactory().glEffectKey(a) == b.getFactory().glEffectKey(b).
The default implementation of this function returns true iff
the two stages have the same return value for numTextures() and
- for texture() over all valid indicse.
+ for texture() over all valid indices.
*/
- virtual bool isEqual(const GrCustomStage&) const;
+ virtual bool isEqual(const GrEffect&) const;
/** Human-meaningful string to identify this effect; may be embedded
in generated shader code. */
- const char* name() const { return this->getFactory().name(); }
+ const char* name() const;
int numTextures() const { return fNumTextures; }
diff --git a/include/gpu/GrEffectStage.h b/include/gpu/GrEffectStage.h
new file mode 100644
index 0000000000..e42f19834a
--- /dev/null
+++ b/include/gpu/GrEffectStage.h
@@ -0,0 +1,129 @@
+
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+
+#ifndef GrEffectStage_DEFINED
+#define GrEffectStage_DEFINED
+
+#include "GrBackendEffectFactory.h"
+#include "GrEffect.h"
+#include "SkMatrix.h"
+#include "GrTypes.h"
+
+#include "SkShader.h"
+
+class GrEffectStage {
+public:
+
+ GrEffectStage()
+ : fEffect (NULL) {
+ GR_DEBUGCODE(fSavedCoordChangeCnt = 0;)
+ }
+
+ ~GrEffectStage() {
+ GrSafeUnref(fEffect);
+ GrAssert(0 == fSavedCoordChangeCnt);
+ }
+
+ bool operator ==(const GrEffectStage& other) const {
+ // first handle cases where one or the other has no effect
+ if (NULL == fEffect) {
+ return NULL == other.fEffect;
+ } else if (NULL == other.fEffect) {
+ return false;
+ }
+
+ if (fEffect->getFactory() != other.fEffect->getFactory()) {
+ return false;
+ }
+
+ if (!fEffect->isEqual(*other.fEffect)) {
+ return false;
+ }
+
+ return fCoordChangeMatrix == other.fCoordChangeMatrix;
+ }
+
+ bool operator !=(const GrEffectStage& s) const { return !(*this == s); }
+
+ GrEffectStage& operator =(const GrEffectStage& other) {
+ GrSafeAssign(fEffect, other.fEffect);
+ if (NULL != fEffect) {
+ fCoordChangeMatrix = other.fCoordChangeMatrix;
+ }
+ return *this;
+ }
+
+ /**
+ * This is called when the coordinate system in which the geometry is specified will change.
+ *
+ * @param matrix The transformation from the old coord system to the new one.
+ */
+ void preConcatCoordChange(const SkMatrix& matrix) { fCoordChangeMatrix.preConcat(matrix); }
+
+ class SavedCoordChange {
+ private:
+ SkMatrix fCoordChangeMatrix;
+ GR_DEBUGCODE(mutable SkAutoTUnref<const GrEffect> fEffect;)
+
+ friend class GrEffectStage;
+ };
+
+ /**
+ * This gets the current coordinate system change. It is the accumulation of
+ * preConcatCoordChange calls since the effect was installed. It is used when then caller
+ * wants to temporarily change the source geometry coord system, draw something, and then
+ * restore the previous coord system (e.g. temporarily draw in device coords).
+ */
+ void saveCoordChange(SavedCoordChange* savedCoordChange) const {
+ savedCoordChange->fCoordChangeMatrix = fCoordChangeMatrix;
+ GrAssert(NULL == savedCoordChange->fEffect.get());
+ GR_DEBUGCODE(GrSafeRef(fEffect);)
+ GR_DEBUGCODE(savedCoordChange->fEffect.reset(fEffect);)
+ GR_DEBUGCODE(++fSavedCoordChangeCnt);
+ }
+
+ /**
+ * This balances the saveCoordChange call.
+ */
+ void restoreCoordChange(const SavedCoordChange& savedCoordChange) {
+ fCoordChangeMatrix = savedCoordChange.fCoordChangeMatrix;
+ GrAssert(savedCoordChange.fEffect.get() == fEffect);
+ GR_DEBUGCODE(--fSavedCoordChangeCnt);
+ GR_DEBUGCODE(savedCoordChange.fEffect.reset(NULL);)
+ }
+
+ /**
+ * Gets the matrix representing all changes of coordinate system since the GrEffect was
+ * installed in the stage.
+ */
+ const SkMatrix& getCoordChangeMatrix() const { return fCoordChangeMatrix; }
+
+ void reset() {
+ GrSafeSetNull(fEffect);
+ }
+
+ const GrEffect* setEffect(const GrEffect* effect) {
+ GrAssert(0 == fSavedCoordChangeCnt);
+ GrSafeAssign(fEffect, effect);
+ fCoordChangeMatrix.reset();
+ return effect;
+ }
+
+ const GrEffect* getEffect() const { return fEffect; }
+
+private:
+ SkMatrix fCoordChangeMatrix;
+ const GrEffect* fEffect;
+
+ GR_DEBUGCODE(mutable int fSavedCoordChangeCnt;)
+};
+
+#endif
+
diff --git a/include/gpu/GrEffectUnitTest.h b/include/gpu/GrEffectUnitTest.h
new file mode 100644
index 0000000000..8cc2689d91
--- /dev/null
+++ b/include/gpu/GrEffectUnitTest.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrEffectUnitTest_DEFINED
+#define GrEffectUnitTest_DEFINED
+
+#include "SkRandom.h"
+#include "GrNoncopyable.h"
+#include "SkTArray.h"
+
+class SkMatrix;
+
+namespace GrEffectUnitTest {
+// Used to access the dummy textures in TestCreate procs.
+enum {
+ kSkiaPMTextureIdx = 0,
+ kAlphaTextureIdx = 1,
+};
+
+/**
+ * A helper for use in GrEffect::TestCreate functions.
+ */
+const SkMatrix& TestMatrix(SkRandom*);
+
+}
+
+#if SK_ALLOW_STATIC_GLOBAL_INITIALIZERS
+
+class GrContext;
+class GrEffect;
+class GrTexture;
+
+class GrEffectTestFactory : GrNoncopyable {
+public:
+
+ typedef GrEffect* (*CreateProc)(SkRandom*, GrContext*, GrTexture* dummyTextures[]);
+
+ GrEffectTestFactory(CreateProc createProc) {
+ fCreateProc = createProc;
+ GetFactories()->push_back(this);
+ }
+
+ static GrEffect* CreateStage(SkRandom* random,
+ GrContext* context,
+ GrTexture* dummyTextures[]) {
+ uint32_t idx = random->nextRangeU(0, GetFactories()->count() - 1);
+ GrEffectTestFactory* factory = (*GetFactories())[idx];
+ return factory->fCreateProc(random, context, dummyTextures);
+ }
+
+private:
+ CreateProc fCreateProc;
+ static SkTArray<GrEffectTestFactory*, true>* GetFactories();
+};
+
+/** GrEffect subclasses should insert this macro in their declaration to be included in the
+ * program generation unit test.
+ */
+#define GR_DECLARE_EFFECT_TEST \
+ static GrEffectTestFactory gTestFactory; \
+ static GrEffect* TestCreate(SkRandom*, GrContext*, GrTexture* dummyTextures[2])
+
+/** GrEffect subclasses should insert this macro in their implementation file. They must then
+ * also implement this static function:
+ * GrEffect* TestCreate(SkRandom*, GrContext*, GrTexture* dummyTextures[2]);
+ * dummyTextures[] are valid textures that can optionally be used to construct GrTextureAccesses.
+ * The first texture has config kSkia8888_PM_GrPixelConfig and the second has
+ * kAlpha_8_GrPixelConfig. TestCreate functions are also free to create additional textures using
+ * the GrContext.
+ */
+#define GR_DEFINE_EFFECT_TEST(Effect) \
+ GrEffectTestFactory Effect :: gTestFactory(Effect :: TestCreate)
+
+#else // !SK_ALLOW_STATIC_GLOBAL_INITIALIZERS
+
+// The unit test relies on static initializers. Just declare the TestCreate function so that
+// its definitions will compile.
+#define GR_DECLARE_EFFECT_TEST \
+ static GrEffect* TestCreate(SkRandom*, GrContext*, GrTexture* dummyTextures[2])
+#define GR_DEFINE_EFFECT_TEST(X)
+
+#endif // !SK_ALLOW_STATIC_GLOBAL_INITIALIZERS
+#endif
diff --git a/include/gpu/GrMatrix.h b/include/gpu/GrMatrix.h
deleted file mode 100644
index 055680ac40..0000000000
--- a/include/gpu/GrMatrix.h
+++ /dev/null
@@ -1,19 +0,0 @@
-
-/*
- * Copyright 2010 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-
-
-#ifndef GrMatrix_DEFINED
-#define GrMatrix_DEFINED
-
-#include "GrRect.h"
-#include "SkMatrix.h"
-
-typedef SkMatrix GrMatrix;
-
-#endif
diff --git a/include/gpu/GrPaint.h b/include/gpu/GrPaint.h
index 3c2966243f..0fd42f9f7f 100644
--- a/include/gpu/GrPaint.h
+++ b/include/gpu/GrPaint.h
@@ -10,9 +10,8 @@
#ifndef GrPaint_DEFINED
#define GrPaint_DEFINED
-#include "GrTexture.h"
#include "GrColor.h"
-#include "GrSamplerState.h"
+#include "GrEffectStage.h"
#include "SkXfermode.h"
@@ -21,13 +20,12 @@
* functions and the how color is blended with the destination pixel.
*
* The paint allows installation of custom color and coverage stages. New types of stages are
- * created by subclassing GrCustomStage.
+ * created by subclassing GrEffect.
*
* The primitive color computation starts with the color specified by setColor(). This color is the
* input to the first color stage. Each color stage feeds its output to the next color stage. The
* final color stage's output color is input to the color filter specified by
- * setXfermodeColorFilter which it turn feeds into the color matrix. The output of the color matrix
- * is the final source color, S.
+ * setXfermodeColorFilter which produces the final source color, S.
*
* Fractional pixel coverage follows a similar flow. The coverage is initially the value specified
* by setCoverage(). This is input to the first coverage stage. Coverage stages are chained
@@ -41,7 +39,7 @@
* Note that the coverage is applied after the blend. This is why they are computed as distinct
* values.
*
- * TODO: Encapsulate setXfermodeColorFilter and color matrix in stages and remove from GrPaint.
+ * TODO: Encapsulate setXfermodeColorFilter in a GrEffect and remove from GrPaint.
*/
class GrPaint {
public:
@@ -105,67 +103,50 @@ public:
GrColor getColorFilterColor() const { return fColorFilterColor; }
/**
- * Turns off application of a color matrix. By default the color matrix is disabled.
- */
- void disableColorMatrix() { fColorMatrixEnabled = false; }
-
- /**
- * Specifies and enables a 4 x 5 color matrix.
- */
- void setColorMatrix(const float matrix[20]) {
- fColorMatrixEnabled = true;
- memcpy(fColorMatrix, matrix, sizeof(fColorMatrix));
- }
-
- bool isColorMatrixEnabled() const { return fColorMatrixEnabled; }
- const float* getColorMatrix() const { return fColorMatrix; }
-
- /**
- * Disables both the matrix and SkXfermode::Mode color filters.
+ * Disables the SkXfermode::Mode color filter.
*/
void resetColorFilter() {
fColorFilterXfermode = SkXfermode::kDst_Mode;
fColorFilterColor = GrColorPackRGBA(0xff, 0xff, 0xff, 0xff);
- fColorMatrixEnabled = false;
}
/**
* Specifies a stage of the color pipeline. Usually the texture matrices of color stages apply
* to the primitive's positions. Some GrContext calls take explicit coords as an array or a
- * rect. In this case these are the pre-matrix coords to colorSampler(0).
+ * rect. In this case these are the pre-matrix coords to colorStage(0).
*/
- GrSamplerState* colorSampler(int i) {
+ GrEffectStage* colorStage(int i) {
GrAssert((unsigned)i < kMaxColorStages);
- return fColorSamplers + i;
+ return fColorStages + i;
}
- const GrSamplerState& getColorSampler(int i) const {
+ const GrEffectStage& getColorStage(int i) const {
GrAssert((unsigned)i < kMaxColorStages);
- return fColorSamplers[i];
+ return fColorStages[i];
}
bool isColorStageEnabled(int i) const {
GrAssert((unsigned)i < kMaxColorStages);
- return (NULL != fColorSamplers[i].getCustomStage());
+ return (NULL != fColorStages[i].getEffect());
}
/**
* Specifies a stage of the coverage pipeline. Coverage stages' texture matrices are always
* applied to the primitive's position, never to explicit texture coords.
*/
- GrSamplerState* coverageSampler(int i) {
+ GrEffectStage* coverageStage(int i) {
GrAssert((unsigned)i < kMaxCoverageStages);
- return fCoverageSamplers + i;
+ return fCoverageStages + i;
}
- const GrSamplerState& getCoverageSampler(int i) const {
+ const GrEffectStage& getCoverageStage(int i) const {
GrAssert((unsigned)i < kMaxCoverageStages);
- return fCoverageSamplers[i];
+ return fCoverageStages[i];
}
bool isCoverageStageEnabled(int i) const {
GrAssert((unsigned)i < kMaxCoverageStages);
- return (NULL != fCoverageSamplers[i].getCustomStage());
+ return (NULL != fCoverageStages[i].getEffect());
}
bool hasCoverageStage() const {
@@ -189,47 +170,49 @@ public:
bool hasStage() const { return this->hasColorStage() || this->hasCoverageStage(); }
/**
- * Preconcats the matrix of all enabled stages with the inverse of a matrix. If the matrix
- * inverse cannot be computed (and there is at least one enabled stage) then false is returned.
+ * Called when the source coord system is changing. preConcatInverse is the inverse of the
+ * transformation from the old coord system to the new coord system. Returns false if the matrix
+ * cannot be inverted.
*/
- bool preConcatSamplerMatricesWithInverse(const GrMatrix& matrix) {
- GrMatrix inv;
+ bool sourceCoordChangeByInverse(const SkMatrix& preConcatInverse) {
+ SkMatrix inv;
bool computed = false;
for (int i = 0; i < kMaxColorStages; ++i) {
if (this->isColorStageEnabled(i)) {
- if (!computed && !matrix.invert(&inv)) {
+ if (!computed && !preConcatInverse.invert(&inv)) {
return false;
} else {
computed = true;
}
- fColorSamplers[i].preConcatMatrix(inv);
+ fColorStages[i].preConcatCoordChange(inv);
}
}
for (int i = 0; i < kMaxCoverageStages; ++i) {
if (this->isCoverageStageEnabled(i)) {
- if (!computed && !matrix.invert(&inv)) {
+ if (!computed && !preConcatInverse.invert(&inv)) {
return false;
} else {
computed = true;
}
- fCoverageSamplers[i].preConcatMatrix(inv);
+ fCoverageStages[i].preConcatCoordChange(inv);
}
}
return true;
}
/**
- * Preconcats the matrix of all stages with a matrix.
+ * Called when the source coord system is changing. preConcat gives the transformation from the
+ * old coord system to the new coord system.
*/
- void preConcatSamplerMatrices(const GrMatrix& matrix) {
+ void sourceCoordChange(const SkMatrix& preConcat) {
for (int i = 0; i < kMaxColorStages; ++i) {
if (this->isColorStageEnabled(i)) {
- fColorSamplers[i].preConcatMatrix(matrix);
+ fColorStages[i].preConcatCoordChange(preConcat);
}
}
for (int i = 0; i < kMaxCoverageStages; ++i) {
if (this->isCoverageStageEnabled(i)) {
- fCoverageSamplers[i].preConcatMatrix(matrix);
+ fCoverageStages[i].preConcatCoordChange(preConcat);
}
}
}
@@ -245,19 +228,15 @@ public:
fColorFilterColor = paint.fColorFilterColor;
fColorFilterXfermode = paint.fColorFilterXfermode;
- fColorMatrixEnabled = paint.fColorMatrixEnabled;
- if (fColorMatrixEnabled) {
- memcpy(fColorMatrix, paint.fColorMatrix, sizeof(fColorMatrix));
- }
for (int i = 0; i < kMaxColorStages; ++i) {
if (paint.isColorStageEnabled(i)) {
- fColorSamplers[i] = paint.fColorSamplers[i];
+ fColorStages[i] = paint.fColorStages[i];
}
}
for (int i = 0; i < kMaxCoverageStages; ++i) {
if (paint.isCoverageStageEnabled(i)) {
- fCoverageSamplers[i] = paint.fCoverageSamplers[i];
+ fCoverageStages[i] = paint.fCoverageStages[i];
}
}
return *this;
@@ -271,9 +250,8 @@ public:
this->resetOptions();
this->resetColor();
this->resetCoverage();
- this->resetTextures();
+ this->resetStages();
this->resetColorFilter();
- this->resetMasks();
}
// internal use
@@ -287,21 +265,19 @@ public:
private:
- GrSamplerState fColorSamplers[kMaxColorStages];
- GrSamplerState fCoverageSamplers[kMaxCoverageStages];
+ GrEffectStage fColorStages[kMaxColorStages];
+ GrEffectStage fCoverageStages[kMaxCoverageStages];
GrBlendCoeff fSrcBlendCoeff;
GrBlendCoeff fDstBlendCoeff;
bool fAntiAlias;
bool fDither;
- bool fColorMatrixEnabled;
GrColor fColor;
uint8_t fCoverage;
GrColor fColorFilterColor;
SkXfermode::Mode fColorFilterXfermode;
- float fColorMatrix[20];
void resetBlend() {
fSrcBlendCoeff = kOne_GrBlendCoeff;
@@ -321,15 +297,12 @@ private:
fCoverage = 0xff;
}
- void resetTextures() {
+ void resetStages() {
for (int i = 0; i < kMaxColorStages; ++i) {
- fColorSamplers[i].reset();
+ fColorStages[i].reset();
}
- }
-
- void resetMasks() {
for (int i = 0; i < kMaxCoverageStages; ++i) {
- fCoverageSamplers[i].reset();
+ fCoverageStages[i].reset();
}
}
};
diff --git a/include/gpu/GrPathRendererChain.h b/include/gpu/GrPathRendererChain.h
new file mode 100644
index 0000000000..d51a4bbf15
--- /dev/null
+++ b/include/gpu/GrPathRendererChain.h
@@ -0,0 +1,83 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#ifndef GrPathRendererChain_DEFINED
+#define GrPathRendererChain_DEFINED
+
+#include "GrRefCnt.h"
+#include "SkTArray.h"
+
+class GrContext;
+class GrDrawTarget;
+class GrPathRenderer;
+class SkPath;
+class SkStrokeRec;
+
+/**
+ * Keeps track of an ordered list of path renderers. When a path needs to be
+ * drawn this list is scanned to find the most preferred renderer. To add your
+ * path renderer to the list implement the GrPathRenderer::AddPathRenderers
+ * function.
+ */
+class GrPathRendererChain : public SkRefCnt {
+public:
+ // See comments in GrPathRenderer.h
+ enum StencilSupport {
+ kNoSupport_StencilSupport,
+ kStencilOnly_StencilSupport,
+ kNoRestriction_StencilSupport,
+ };
+
+ SK_DECLARE_INST_COUNT(GrPathRendererChain)
+
+ GrPathRendererChain(GrContext* context);
+
+ ~GrPathRendererChain();
+
+ // takes a ref and unrefs in destructor
+ GrPathRenderer* addPathRenderer(GrPathRenderer* pr);
+
+ /** Documents how the caller plans to use a GrPathRenderer to draw a path. It affects the PR
+ returned by getPathRenderer */
+ enum DrawType {
+ kColor_DrawType, // draw to the color buffer, no AA
+ kColorAntiAlias_DrawType, // draw to color buffer, with partial coverage AA
+ kStencilOnly_DrawType, // draw just to the stencil buffer
+ kStencilAndColor_DrawType, // draw the stencil and color buffer, no AA
+ kStencilAndColorAntiAlias_DrawType // draw the stencil and color buffer, with partial
+ // coverage AA.
+ };
+ /** Returns a GrPathRenderer compatible with the request if one is available. If the caller
+ is drawing the path to the stencil buffer then stencilSupport can be used to determine
+ whether the path can be rendered with arbitrary stencil rules or not. See comments on
+ StencilSupport in GrPathRenderer.h. */
+ GrPathRenderer* getPathRenderer(const SkPath& path,
+ const SkStrokeRec& rec,
+ const GrDrawTarget* target,
+ DrawType drawType,
+ StencilSupport* stencilSupport);
+
+private:
+
+ GrPathRendererChain();
+
+ void init();
+
+ enum {
+ kPreAllocCount = 8,
+ };
+ bool fInit;
+ GrContext* fOwner;
+ SkSTArray<kPreAllocCount, GrPathRenderer*, true> fChain;
+
+ typedef SkRefCnt INHERITED;
+};
+
+
+#endif
diff --git a/include/gpu/GrPoint.h b/include/gpu/GrPoint.h
index fd2c5a7cc1..302c4e1f27 100644
--- a/include/gpu/GrPoint.h
+++ b/include/gpu/GrPoint.h
@@ -12,7 +12,7 @@
#define GrPoint_DEFINED
#include "GrTypes.h"
-#include "GrScalar.h"
+#include "SkScalar.h"
#include "SkPoint.h"
#define GrPoint SkPoint
diff --git a/include/gpu/GrProgramStageFactory.h b/include/gpu/GrProgramStageFactory.h
deleted file mode 100644
index 48e8408548..0000000000
--- a/include/gpu/GrProgramStageFactory.h
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright 2012 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef GrProgramStageFactory_DEFINED
-#define GrProgramStageFactory_DEFINED
-
-#include "GrTypes.h"
-#include "SkTemplates.h"
-#include "GrNoncopyable.h"
-
-/** Given a GrCustomStage of a particular type, creates the corresponding
- graphics-backend-specific GrProgramStage. Also tracks equivalence
- of shaders generated via a key.
- */
-
-class GrCustomStage;
-class GrGLProgramStage;
-class GrGLCaps;
-
-class GrProgramStageFactory : public GrNoncopyable {
-public:
- typedef uint32_t StageKey;
- enum {
- kProgramStageKeyBits = 10,
- kTexturingStageKeyBits = 6
- };
-
- virtual StageKey glStageKey(const GrCustomStage& stage,
- const GrGLCaps& caps ) const = 0;
- virtual GrGLProgramStage* createGLInstance(
- const GrCustomStage& stage) const = 0;
-
- bool operator ==(const GrProgramStageFactory& b) const {
- return fStageClassID == b.fStageClassID;
- }
- bool operator !=(const GrProgramStageFactory& b) const {
- return !(*this == b);
- }
-
- virtual const char* name() const = 0;
-
-protected:
- enum {
- kIllegalStageClassID = 0,
- };
-
- GrProgramStageFactory() {
- fStageClassID = kIllegalStageClassID;
- }
-
- virtual ~GrProgramStageFactory() {}
-
- static StageKey GenID() {
- // fCurrStageClassID has been initialized to kIllegalStageClassID. The
- // atomic inc returns the old value not the incremented value. So we add
- // 1 to the returned value.
- int32_t id = sk_atomic_inc(&fCurrStageClassID) + 1;
- GrAssert(id < (1 << (8 * sizeof(StageKey) - kProgramStageKeyBits)));
- return id;
- }
-
- StageKey fStageClassID;
-
-private:
- static int32_t fCurrStageClassID;
-};
-
-template <typename StageClass>
-class GrTProgramStageFactory : public GrProgramStageFactory {
-
-public:
- typedef typename StageClass::GLProgramStage GLProgramStage;
-
- /** Returns a human-readable name that is accessible via GrCustomStage or
- GrGLProgramStage and is consistent between the two of them.
- */
- virtual const char* name() const SK_OVERRIDE { return StageClass::Name(); }
-
- /** Returns a value that idenitifes the GLSL shader code generated by
- a GrCustomStage. This enables caching of generated shaders. Part of the
- id identifies the GrCustomShader subclass. The remainder is based
- on the aspects of the GrCustomStage object's configuration that affect
- GLSL code generation. */
- virtual StageKey glStageKey(const GrCustomStage& stage,
- const GrGLCaps& caps) const SK_OVERRIDE {
- GrAssert(kIllegalStageClassID != fStageClassID);
- StageKey stageID = GLProgramStage::GenKey(stage, caps);
- StageKey textureKey = GLProgramStage::GenTextureKey(stage, caps);
-#if GR_DEBUG
- static const StageKey kIllegalIDMask =
- (uint16_t) (~((1U << kProgramStageKeyBits) - 1));
- GrAssert(!(kIllegalIDMask & stageID));
-
- static const StageKey kIllegalTextureKeyMask =
- (uint16_t) (~((1U << kTexturingStageKeyBits) - 1));
- GrAssert(!(kIllegalTextureKeyMask & textureKey));
-#endif
- return fStageClassID | (textureKey << kProgramStageKeyBits) | stageID;
- }
-
- /** Returns a new instance of the appropriate *GL* implementation class
- for the given GrCustomStage; caller is responsible for deleting
- the object. */
- virtual GLProgramStage* createGLInstance(
- const GrCustomStage& stage) const SK_OVERRIDE {
- return SkNEW_ARGS(GLProgramStage, (*this, stage));
- }
-
- /** This class is a singleton. This function returns the single instance.
- */
- static const GrProgramStageFactory& getInstance() {
- static SkAlignedSTStorage<1, GrTProgramStageFactory> gInstanceMem;
- static const GrTProgramStageFactory* gInstance;
- if (!gInstance) {
- gInstance = SkNEW_PLACEMENT(gInstanceMem.get(),
- GrTProgramStageFactory);
- }
- return *gInstance;
- }
-
-protected:
- GrTProgramStageFactory() {
- fStageClassID = GenID() << (kProgramStageKeyBits + kTexturingStageKeyBits) ;
- }
-};
-
-#endif
diff --git a/include/gpu/GrRenderTarget.h b/include/gpu/GrRenderTarget.h
index f3add50ee3..9964c9b7ed 100644
--- a/include/gpu/GrRenderTarget.h
+++ b/include/gpu/GrRenderTarget.h
@@ -1,20 +1,10 @@
/*
- Copyright 2011 Google Inc.
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
*/
-
#ifndef GrRenderTarget_DEFINED
#define GrRenderTarget_DEFINED
@@ -40,7 +30,7 @@ public:
// GrSurface overrides
/**
- * @return the texture associated with the rendertarget, may be NULL.
+ * @return the texture associated with the render target, may be NULL.
*/
virtual GrTexture* asTexture() SK_OVERRIDE { return fTexture; }
virtual const GrTexture* asTexture() const SK_OVERRIDE { return fTexture; }
@@ -70,15 +60,15 @@ public:
* If this RT is multisampled, this is the multisample buffer
* @return the 3D API's handle to this object (e.g. FBO ID in OpenGL)
*/
- virtual intptr_t getRenderTargetHandle() const = 0;
+ virtual GrBackendObject getRenderTargetHandle() const = 0;
/**
* If this RT is multisampled, this is the buffer it is resolved to.
* Otherwise, same as getRenderTargetHandle().
- * (In GL a separate FBO ID is used for the msaa and resolved buffers)
+ * (In GL a separate FBO ID is used for the MSAA and resolved buffers)
* @return the 3D API's handle to this object (e.g. FBO ID in OpenGL)
*/
- virtual intptr_t getRenderTargetResolvedHandle() const = 0;
+ virtual GrBackendObject getRenderTargetResolvedHandle() const = 0;
/**
* @return true if the surface is multisampled, false otherwise
@@ -94,9 +84,9 @@ public:
* Call to indicate the multisample contents were modified such that the
* render target needs to be resolved before it can be used as texture. Gr
* tracks this for its own drawing and thus this only needs to be called
- * when the render target has been modified outside of Gr. Only meaningful
- * for Gr-created RT/Textures and Platform RT/Textures created with the
- * kGrCanResolve flag.
+ * when the render target has been modified outside of Gr. This has no
+ * effect on wrapped backend render targets.
+ *
* @param rect a rect bounding the area needing resolve. NULL indicates
* the whole RT needs resolving.
*/
@@ -150,15 +140,16 @@ public:
protected:
GrRenderTarget(GrGpu* gpu,
GrTexture* texture,
- const GrTextureDesc& desc)
- : INHERITED(gpu, desc)
+ const GrTextureDesc& desc,
+ Origin origin)
+ : INHERITED(gpu, desc, origin)
, fStencilBuffer(NULL)
, fTexture(texture) {
fResolveRect.setLargestInverted();
}
friend class GrTexture;
- // When a texture unrefs an owned rendertarget this func
+ // When a texture unrefs an owned render target this func
// removes the back pointer. This could be called from
// texture's destructor but would have to be done in derived
// classes. By the time of texture base destructor it has already
diff --git a/include/gpu/GrResource.h b/include/gpu/GrResource.h
index aeab180226..3c306f88c6 100644
--- a/include/gpu/GrResource.h
+++ b/include/gpu/GrResource.h
@@ -12,7 +12,7 @@
#include "GrRefCnt.h"
-#include "SkTDLinkedList.h"
+#include "SkTInternalLList.h"
class GrGpu;
class GrContext;
@@ -93,8 +93,8 @@ private:
// release() on all such resources in its
// destructor.
- // we're a dlinklist
- SK_DEFINE_DLINKEDLIST_INTERFACE(GrResource);
+ // We're in an internal doubly linked list
+ SK_DECLARE_INTERNAL_LLIST_INTERFACE(GrResource);
GrResourceEntry* fCacheEntry; // NULL if not in cache
diff --git a/include/gpu/GrSamplerState.h b/include/gpu/GrSamplerState.h
deleted file mode 100644
index da52e95277..0000000000
--- a/include/gpu/GrSamplerState.h
+++ /dev/null
@@ -1,101 +0,0 @@
-
-/*
- * Copyright 2010 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-
-
-#ifndef GrSamplerState_DEFINED
-#define GrSamplerState_DEFINED
-
-#include "GrCustomStage.h"
-#include "GrMatrix.h"
-#include "GrTypes.h"
-
-#include "SkShader.h"
-
-class GrSamplerState {
-public:
-
- GrSamplerState()
- : fCustomStage (NULL) {
- memset(this, 0, sizeof(GrSamplerState));
- this->reset();
- }
-
- ~GrSamplerState() {
- GrSafeUnref(fCustomStage);
- }
-
- bool operator ==(const GrSamplerState& s) const {
- /* We must be bit-identical as far as the CustomStage;
- there may be multiple CustomStages that will produce
- the same shader code and so are equivalent.
- Can't take the address of fWrapX because it's :8 */
- int bitwiseRegion = (intptr_t) &fCustomStage - (intptr_t) this;
- GrAssert(sizeof(GrSamplerState) ==
- bitwiseRegion + sizeof(fCustomStage));
- return !memcmp(this, &s, bitwiseRegion) &&
- ((fCustomStage == s.fCustomStage) ||
- (fCustomStage && s.fCustomStage &&
- (fCustomStage->getFactory() ==
- s.fCustomStage->getFactory()) &&
- fCustomStage->isEqual(*s.fCustomStage)));
- }
- bool operator !=(const GrSamplerState& s) const { return !(*this == s); }
-
- GrSamplerState& operator =(const GrSamplerState& s) {
- fMatrix = s.fMatrix;
- GrSafeAssign(fCustomStage, s.fCustomStage);
- return *this;
- }
-
- const GrMatrix& getMatrix() const { return fMatrix; }
-
- /**
- * Multiplies the current sampler matrix a matrix
- *
- * After this call M' = M*m where M is the old matrix, m is the parameter
- * to this function, and M' is the new matrix. (We consider points to
- * be column vectors so tex cood vector t is transformed by matrix X as
- * t' = X*t.)
- *
- * @param matrix the matrix used to modify the matrix.
- */
- void preConcatMatrix(const GrMatrix& matrix) { fMatrix.preConcat(matrix); }
-
- /**
- * Do not call this function. It will be removed soon.
- */
- void setMatrixDeprecated(const GrMatrix& matrix) { fMatrix = matrix; }
-
- void reset() {
- fMatrix.reset();
- GrSafeSetNull(fCustomStage);
- }
-
- GrCustomStage* setCustomStage(GrCustomStage* stage) {
- GrSafeAssign(fCustomStage, stage);
- fMatrix.reset();
- return stage;
- }
-
- GrCustomStage* setCustomStage(GrCustomStage* stage, const GrMatrix& matrix) {
- GrSafeAssign(fCustomStage, stage);
- fMatrix = matrix;
- return stage;
- }
-
- const GrCustomStage* getCustomStage() const { return fCustomStage; }
-
-private:
- GrMatrix fMatrix;
-
- GrCustomStage* fCustomStage;
-};
-
-#endif
-
diff --git a/include/gpu/GrScalar.h b/include/gpu/GrScalar.h
deleted file mode 100644
index 5d2f13ba19..0000000000
--- a/include/gpu/GrScalar.h
+++ /dev/null
@@ -1,49 +0,0 @@
-
-/*
- * Copyright 2010 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-
-
-#ifndef GrScalar_DEFINED
-#define GrScalar_DEFINED
-
-#include "GrTypes.h"
-#include "SkScalar.h"
-
-#define GR_Int32Min SK_NaN32
-#define GR_Int32Max SK_MaxS32
-
-#define GR_Fixed1 SK_Fixed1
-#define GR_FixedHalf SK_FixedHalf
-#define GrIntToFixed(a) SkIntToFixed(a)
-#define GrFixedToFloat(a) SkFixedToFloat(a)
-#define GrFixedFloorToInt(a) SkFixedFloor(a)
-
-#define GrScalar SkScalar
-#define GR_Scalar1 SK_Scalar1
-#define GR_ScalarHalf SK_ScalarHalf
-#define GR_ScalarMin SK_ScalarMin
-#define GR_ScalarMax SK_ScalarMax
-
-#define GrIntToScalar(a) SkIntToScalar(a)
-#define GrScalarHalf(a) SkScalarHalf(a)
-#define GrScalarAve(a,b) SkScalarAve(a,b)
-#define GrMul(a,b) SkScalarMul(a,b) // deprecated, prefer GrScalarMul
-#define GrScalarMul(a,b) SkScalarMul(a,b)
-#define GrScalarDiv(a,b) SkScalarDiv(a, b)
-#define GrScalarToFloat(a) SkScalarToFloat(a)
-#define GrFloatToScalar(a) SkFloatToScalar(a)
-#define GrIntToScalar(a) SkIntToScalar(a)
-#define GrScalarAbs(a) SkScalarAbs(a)
-#define GrScalarIsInt(a) SkScalarIsInt(a)
-#define GrScalarMax(a,b) SkScalarMax(a,b)
-#define GrScalarFloorToInt(a) SkScalarFloor(a)
-#define GrScalarCeilToInt(a) SkScalarCeil(a)
-#define GrFixedToScalar(a) SkFixedToScalar(a)
-
-#endif
-
diff --git a/include/gpu/GrSurface.h b/include/gpu/GrSurface.h
index aa237aee4d..d7aa2672d8 100644
--- a/include/gpu/GrSurface.h
+++ b/include/gpu/GrSurface.h
@@ -34,6 +34,22 @@ public:
int height() const { return fDesc.fHeight; }
/**
+ * Some surfaces will be stored such that the upper and left edges of the content meet at the
+ * the origin (in texture coord space) and for other surfaces the lower and left edges meet at
+ * the origin. Render-targets are always consistent with the convention of the underlying
+ * backend API to make it easier to mix native backend rendering with Skia rendering. Wrapped
+ * backend surfaces always use the backend's convention as well.
+ */
+ enum Origin {
+ kTopLeft_Origin,
+ kBottomLeft_Origin,
+ };
+ Origin origin() const {
+ GrAssert(kTopLeft_Origin == fOrigin || kBottomLeft_Origin == fOrigin);
+ return fOrigin;
+ }
+
+ /**
* Retrieves the pixel config specified when the surface was created.
* For render targets this can be kUnknown_GrPixelConfig
* if client asked us to render to a target that has a pixel
@@ -66,7 +82,7 @@ public:
* @param height height of rectangle to read in pixels.
* @param config the pixel config of the destination buffer
* @param buffer memory to read the rectangle into.
- * @param rowBytes number of bytes bewtween consecutive rows. Zero means rows are tightly
+ * @param rowBytes number of bytes between consecutive rows. Zero means rows are tightly
* packed.
* @param pixelOpsFlags See the GrContext::PixelOpsFlags enum.
*
@@ -88,7 +104,7 @@ public:
* @param height height of rectangle to write in pixels.
* @param config the pixel config of the source buffer
* @param buffer memory to read the rectangle from.
- * @param rowBytes number of bytes bewtween consecutive rows. Zero means rows are tightly
+ * @param rowBytes number of bytes between consecutive rows. Zero means rows are tightly
* packed.
* @param pixelOpsFlags See the GrContext::PixelOpsFlags enum.
*/
@@ -99,14 +115,17 @@ public:
uint32_t pixelOpsFlags = 0) = 0;
protected:
- GrTextureDesc fDesc;
-
- GrSurface(GrGpu* gpu, const GrTextureDesc& desc)
+ GrSurface(GrGpu* gpu, const GrTextureDesc& desc, Origin origin)
: INHERITED(gpu)
- , fDesc(desc) {
+ , fDesc(desc)
+ , fOrigin(origin) {
}
+ GrTextureDesc fDesc;
+
private:
+ Origin fOrigin;
+
typedef GrResource INHERITED;
};
diff --git a/include/gpu/GrTBackendEffectFactory.h b/include/gpu/GrTBackendEffectFactory.h
new file mode 100644
index 0000000000..52dbc64dd8
--- /dev/null
+++ b/include/gpu/GrTBackendEffectFactory.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrTBackendEffectFactory_DEFINED
+#define GrTBackendEffectFactory_DEFINED
+
+#include "GrBackendEffectFactory.h"
+#include "GrEffectStage.h"
+
+/**
+ * Implements GrBackendEffectFactory for a GrEffect subclass as a singleton.
+ */
+template <typename EffectClass>
+class GrTBackendEffectFactory : public GrBackendEffectFactory {
+
+public:
+ typedef typename EffectClass::GLEffect GLEffect;
+
+ /** Returns a human-readable name that is accessible via GrEffect or
+ GrGLEffect and is consistent between the two of them.
+ */
+ virtual const char* name() const SK_OVERRIDE { return EffectClass::Name(); }
+
+ /** Returns a value that identifies the GLSL shader code generated by
+ a GrEffect. This enables caching of generated shaders. Part of the
+ id identifies the GrEffect subclass. The remainder is based
+ on the aspects of the GrEffect object's configuration that affect
+ GLSL code generation. */
+ virtual EffectKey glEffectKey(const GrEffectStage& stage,
+ const GrGLCaps& caps) const SK_OVERRIDE {
+ GrAssert(kIllegalEffectClassID != fEffectClassID);
+ EffectKey effectKey = GLEffect::GenKey(stage, caps);
+ EffectKey textureKey = GLEffect::GenTextureKey(*stage.getEffect(), caps);
+#if GR_DEBUG
+ static const EffectKey kIllegalIDMask = (uint16_t) (~((1U << kEffectKeyBits) - 1));
+ GrAssert(!(kIllegalIDMask & effectKey));
+
+ static const EffectKey kIllegalTextureKeyMask = (uint16_t) (~((1U << kTextureKeyBits) - 1));
+ GrAssert(!(kIllegalTextureKeyMask & textureKey));
+#endif
+ return fEffectClassID | (textureKey << kEffectKeyBits) | effectKey;
+ }
+
+ /** Returns a new instance of the appropriate *GL* implementation class
+ for the given GrEffect; caller is responsible for deleting
+ the object. */
+ virtual GLEffect* createGLInstance(const GrEffect& effect) const SK_OVERRIDE {
+ return SkNEW_ARGS(GLEffect, (*this, effect));
+ }
+
+ /** This class is a singleton. This function returns the single instance.
+ */
+ static const GrBackendEffectFactory& getInstance() {
+ static SkAlignedSTStorage<1, GrTBackendEffectFactory> gInstanceMem;
+ static const GrTBackendEffectFactory* gInstance;
+ if (!gInstance) {
+ gInstance = SkNEW_PLACEMENT(gInstanceMem.get(),
+ GrTBackendEffectFactory);
+ }
+ return *gInstance;
+ }
+
+protected:
+ GrTBackendEffectFactory() {
+ fEffectClassID = GenID() << (kEffectKeyBits + kTextureKeyBits) ;
+ }
+};
+
+#endif
diff --git a/include/gpu/GrTexture.h b/include/gpu/GrTexture.h
index 5f6d53522a..d41be11b61 100644
--- a/include/gpu/GrTexture.h
+++ b/include/gpu/GrTexture.h
@@ -111,9 +111,9 @@ public:
/**
* Return the native ID or handle to the texture, depending on the
- * platform. e.g. on opengl, return the texture ID.
+ * platform. e.g. on OpenGL, return the texture ID.
*/
- virtual intptr_t getTextureHandle() const = 0;
+ virtual GrBackendObject getTextureHandle() const = 0;
/**
* Call this when the state of the native API texture object is
@@ -146,13 +146,13 @@ protected:
// base class cons sets to NULL
// subclass cons can create and set
- GrTexture(GrGpu* gpu, const GrTextureDesc& desc)
- : INHERITED(gpu, desc)
+ GrTexture(GrGpu* gpu, const GrTextureDesc& desc, Origin origin)
+ : INHERITED(gpu, desc, origin)
, fRenderTarget(NULL) {
// only make sense if alloc size is pow2
- fShiftFixedX = 31 - Gr_clz(fDesc.fWidth);
- fShiftFixedY = 31 - Gr_clz(fDesc.fHeight);
+ fShiftFixedX = 31 - SkCLZ(fDesc.fWidth);
+ fShiftFixedY = 31 - SkCLZ(fDesc.fHeight);
}
// GrResource overrides
diff --git a/include/gpu/GrTextureAccess.h b/include/gpu/GrTextureAccess.h
index 0d0860f2fe..b03e6e6dc9 100644
--- a/include/gpu/GrTextureAccess.h
+++ b/include/gpu/GrTextureAccess.h
@@ -103,14 +103,14 @@ private:
/** A class representing the swizzle access pattern for a texture. Note that if the texture is
* an alpha-only texture then the alpha channel is substituted for other components. Any mangling
* to handle the r,g,b->a conversions for alpha textures is automatically included in the stage
- * key. However, if a GrCustomStage uses different swizzles based on its input then it must
+ * key. However, if a GrEffect uses different swizzles based on its input then it must
* consider that variation in its key-generation.
*/
class GrTextureAccess : GrNoncopyable {
public:
/**
- * A default GrTextureAccess must have reset() called on it in a GrCustomStage subclass's
- * constructor if it will be accessible via GrCustomStage::textureAccess().
+ * A default GrTextureAccess must have reset() called on it in a GrEffect subclass's
+ * constructor if it will be accessible via GrEffect::textureAccess().
*/
GrTextureAccess();
diff --git a/include/gpu/GrTypes.h b/include/gpu/GrTypes.h
index f084427e6f..21ae6deca5 100644
--- a/include/gpu/GrTypes.h
+++ b/include/gpu/GrTypes.h
@@ -13,6 +13,7 @@
#include "SkTypes.h"
#include "GrConfig.h"
+#include "SkMath.h"
////////////////////////////////////////////////////////////////////////////////
@@ -137,11 +138,6 @@ static inline void Gr_bzero(void* dst, size_t size) {
///////////////////////////////////////////////////////////////////////////////
/**
- * Return the number of leading zeros in n
- */
-extern int Gr_clz(uint32_t n);
-
-/**
* Return true if n is a power of 2
*/
static inline bool GrIsPow2(unsigned n) {
@@ -152,12 +148,12 @@ static inline bool GrIsPow2(unsigned n) {
* Return the next power of 2 >= n.
*/
static inline uint32_t GrNextPow2(uint32_t n) {
- return n ? (1 << (32 - Gr_clz(n - 1))) : 1;
+ return n ? (1 << (32 - SkCLZ(n - 1))) : 1;
}
static inline int GrNextPow2(int n) {
GrAssert(n >= 0); // this impl only works for non-neg.
- return n ? (1 << (32 - Gr_clz(n - 1))) : 1;
+ return n ? (1 << (32 - SkCLZ(n - 1))) : 1;
}
///////////////////////////////////////////////////////////////////////////////
@@ -186,16 +182,15 @@ static inline int16_t GrToS16(intptr_t x) {
/**
* Possible 3D APIs that may be used by Ganesh.
*/
-enum GrEngine {
- kOpenGL_Shaders_GrEngine,
- kOpenGL_Fixed_GrEngine,
+enum GrBackend {
+ kOpenGL_GrBackend,
};
/**
- * Engine-specific 3D context handle
+ * Backend-specific 3D context handle
* GrGLInterface* for OpenGL. If NULL will use the default GL interface.
*/
-typedef intptr_t GrPlatform3DContext;
+typedef intptr_t GrBackendContext;
///////////////////////////////////////////////////////////////////////////////
@@ -458,7 +453,7 @@ struct GrTextureDesc {
* applies if the kRenderTarget_GrTextureFlagBit is set. The actual number
* of samples may not exactly match the request. The request will be rounded
* up to the next supported sample count, or down if it is larger than the
- * max supportex count.
+ * max supported count.
*/
int fSampleCnt;
};
@@ -545,57 +540,10 @@ static int inline NumPathCmdPoints(GrPathCmd cmd) {
return gNumPoints[cmd];
}
-/**
- * Path filling rules
- */
-enum GrPathFill {
- kWinding_GrPathFill,
- kEvenOdd_GrPathFill,
- kInverseWinding_GrPathFill,
- kInverseEvenOdd_GrPathFill,
- kHairLine_GrPathFill,
-
- kGrPathFillCount
-};
-
-static inline GrPathFill GrNonInvertedFill(GrPathFill fill) {
- static const GrPathFill gNonInvertedFills[] = {
- kWinding_GrPathFill, // kWinding_GrPathFill
- kEvenOdd_GrPathFill, // kEvenOdd_GrPathFill
- kWinding_GrPathFill, // kInverseWinding_GrPathFill
- kEvenOdd_GrPathFill, // kInverseEvenOdd_GrPathFill
- kHairLine_GrPathFill,// kHairLine_GrPathFill
- };
- GR_STATIC_ASSERT(0 == kWinding_GrPathFill);
- GR_STATIC_ASSERT(1 == kEvenOdd_GrPathFill);
- GR_STATIC_ASSERT(2 == kInverseWinding_GrPathFill);
- GR_STATIC_ASSERT(3 == kInverseEvenOdd_GrPathFill);
- GR_STATIC_ASSERT(4 == kHairLine_GrPathFill);
- GR_STATIC_ASSERT(5 == kGrPathFillCount);
- return gNonInvertedFills[fill];
-}
-
-static inline bool GrIsFillInverted(GrPathFill fill) {
- static const bool gIsFillInverted[] = {
- false, // kWinding_GrPathFill
- false, // kEvenOdd_GrPathFill
- true, // kInverseWinding_GrPathFill
- true, // kInverseEvenOdd_GrPathFill
- false, // kHairLine_GrPathFill
- };
- GR_STATIC_ASSERT(0 == kWinding_GrPathFill);
- GR_STATIC_ASSERT(1 == kEvenOdd_GrPathFill);
- GR_STATIC_ASSERT(2 == kInverseWinding_GrPathFill);
- GR_STATIC_ASSERT(3 == kInverseEvenOdd_GrPathFill);
- GR_STATIC_ASSERT(4 == kHairLine_GrPathFill);
- GR_STATIC_ASSERT(5 == kGrPathFillCount);
- return gIsFillInverted[fill];
-}
-
///////////////////////////////////////////////////////////////////////////////
// opaque type for 3D API object handles
-typedef intptr_t GrPlatform3DObject;
+typedef intptr_t GrBackendObject;
/**
* Gr can wrap an existing texture created by the client with a GrTexture
@@ -618,11 +566,11 @@ typedef intptr_t GrPlatform3DObject;
* Note: These flags currently form a subset of GrTexture's flags.
*/
-enum GrPlatformTextureFlags {
+enum GrBackendTextureFlags {
/**
* No flags enabled
*/
- kNone_GrPlatformTextureFlag = kNone_GrTextureFlags,
+ kNone_GrBackendTextureFlag = kNone_GrTextureFlags,
/**
* Indicates that the texture is also a render target, and thus should have
* a GrRenderTarget object.
@@ -630,13 +578,13 @@ enum GrPlatformTextureFlags {
* D3D (future): client must have created the texture with flags that allow
* it to be used as a render target.
*/
- kRenderTarget_GrPlatformTextureFlag = kRenderTarget_GrTextureFlagBit,
+ kRenderTarget_GrBackendTextureFlag = kRenderTarget_GrTextureFlagBit,
};
-GR_MAKE_BITFIELD_OPS(GrPlatformTextureFlags)
+GR_MAKE_BITFIELD_OPS(GrBackendTextureFlags)
-struct GrPlatformTextureDesc {
- GrPlatformTextureDesc() { memset(this, 0, sizeof(*this)); }
- GrPlatformTextureFlags fFlags;
+struct GrBackendTextureDesc {
+ GrBackendTextureDesc() { memset(this, 0, sizeof(*this)); }
+ GrBackendTextureFlags fFlags;
int fWidth; //<! width in pixels
int fHeight; //<! height in pixels
GrPixelConfig fConfig; //<! color format
@@ -649,7 +597,7 @@ struct GrPlatformTextureDesc {
* Handle to the 3D API object.
* OpenGL: Texture ID.
*/
- GrPlatform3DObject fTextureHandle;
+ GrBackendObject fTextureHandle;
};
///////////////////////////////////////////////////////////////////////////////
@@ -664,14 +612,14 @@ struct GrPlatformTextureDesc {
* the 3D API doesn't require this (OpenGL).
*/
-struct GrPlatformRenderTargetDesc {
- GrPlatformRenderTargetDesc() { memset(this, 0, sizeof(*this)); }
+struct GrBackendRenderTargetDesc {
+ GrBackendRenderTargetDesc() { memset(this, 0, sizeof(*this)); }
int fWidth; //<! width in pixels
int fHeight; //<! height in pixels
GrPixelConfig fConfig; //<! color format
/**
* The number of samples per pixel. Gr uses this to influence decisions
- * about applying other forms of antialiasing.
+ * about applying other forms of anti-aliasing.
*/
int fSampleCnt;
/**
@@ -682,9 +630,26 @@ struct GrPlatformRenderTargetDesc {
* Handle to the 3D API object.
* OpenGL: FBO ID
*/
- GrPlatform3DObject fRenderTargetHandle;
+ GrBackendObject fRenderTargetHandle;
};
+///////////////////////////////////////////////////////////////////////////////
+// Legacy names that will be kept until WebKit can be updated.
+
+typedef GrBackend GrEngine;
+static const GrBackend kOpenGL_Shaders_GrEngine = kOpenGL_GrBackend;
+
+typedef GrBackendContext GrPlatform3DContext;
+
+typedef GrBackendObject GrPlatform3DObject;
+
+typedef GrBackendTextureFlags GrPlatformTextureFlags;
+static const GrBackendTextureFlags kNone_GrPlatformTextureFlag = kNone_GrBackendTextureFlag;
+static const GrBackendTextureFlags kRenderTarget_GrPlatformTextureFlag = kRenderTarget_GrBackendTextureFlag;
+
+typedef GrBackendTextureDesc GrPlatformTextureDesc;
+
+typedef GrBackendRenderTargetDesc GrPlatformRenderTargetDesc;
///////////////////////////////////////////////////////////////////////////////
diff --git a/include/gpu/GrUserConfig.h b/include/gpu/GrUserConfig.h
index d514486d24..3eb77a6891 100644
--- a/include/gpu/GrUserConfig.h
+++ b/include/gpu/GrUserConfig.h
@@ -54,12 +54,15 @@
*/
//#define GR_GEOM_BUFFER_LOCK_THRESHOLD (1<<15)
+/**
+ * This gives a threshold in megabytes for the maximum size of the texture cache
+ * in vram. The value is only a default and can be overridden at runtime.
+ */
+//#define GR_DEFAULT_TEXTURE_CACHE_MB_LIMIT 96
+
///////////////////////////////////////////////////////////////////////////////
// Decide Ganesh types
-#define GR_SCALAR_IS_FIXED 0
-#define GR_SCALAR_IS_FLOAT 1
-
#define GR_TEXT_SCALAR_IS_USHORT 0
#define GR_TEXT_SCALAR_IS_FIXED 0
#define GR_TEXT_SCALAR_IS_FLOAT 1
diff --git a/include/gpu/SkGpuDevice.h b/include/gpu/SkGpuDevice.h
index a1d00ba35c..1e78b4a2b9 100644
--- a/include/gpu/SkGpuDevice.h
+++ b/include/gpu/SkGpuDevice.h
@@ -29,11 +29,11 @@ class SK_API SkGpuDevice : public SkDevice {
public:
/**
* New device that will create an offscreen renderTarget based on the
- * config, width, height. The device's storage will not count against
- * the GrContext's texture cache budget. The device's pixels will be
- * uninitialized.
+ * config, width, height, and sampleCount. The device's storage will not
+ * count against the GrContext's texture cache budget. The device's pixels
+ * will be uninitialized.
*/
- SkGpuDevice(GrContext*, SkBitmap::Config, int width, int height);
+ SkGpuDevice(GrContext*, SkBitmap::Config, int width, int height, int sampleCount = 0);
/**
* New device that will render to the specified renderTarget.
diff --git a/include/gpu/SkGrPixelRef.h b/include/gpu/SkGrPixelRef.h
index 4476a84a6f..0a32f21064 100644
--- a/include/gpu/SkGrPixelRef.h
+++ b/include/gpu/SkGrPixelRef.h
@@ -58,7 +58,7 @@ public:
protected:
// overrides from SkPixelRef
virtual bool onReadPixels(SkBitmap* dst, const SkIRect* subset) SK_OVERRIDE;
- virtual SkPixelRef* deepCopy(SkBitmap::Config dstConfig) SK_OVERRIDE;
+ virtual SkPixelRef* deepCopy(SkBitmap::Config dstConfig, const SkIRect* subset) SK_OVERRIDE;
private:
GrSurface* fSurface;
diff --git a/include/gpu/gl/GrGLFunctions.h b/include/gpu/gl/GrGLFunctions.h
index b0b3b16313..ceaecc6e0f 100644
--- a/include/gpu/gl/GrGLFunctions.h
+++ b/include/gpu/gl/GrGLFunctions.h
@@ -122,7 +122,11 @@ extern "C" {
typedef GrGLvoid (GR_GL_FUNCTION_TYPE* GrGLRenderbufferStorageMultisampleCoverageProc)(GrGLenum target, GrGLsizei coverageSamples, GrGLsizei colorSamples, GrGLenum internalformat, GrGLsizei width, GrGLsizei height);
typedef GrGLvoid (GR_GL_FUNCTION_TYPE* GrGLResolveMultisampleFramebufferProc)();
typedef GrGLvoid (GR_GL_FUNCTION_TYPE* GrGLScissorProc)(GrGLint x, GrGLint y, GrGLsizei width, GrGLsizei height);
+#if GR_USE_NEW_GL_SHADER_SOURCE_SIGNATURE
+ typedef GrGLvoid (GR_GL_FUNCTION_TYPE* GrGLShaderSourceProc)(GrGLuint shader, GrGLsizei count, const char* const * str, const GrGLint* length);
+#else
typedef GrGLvoid (GR_GL_FUNCTION_TYPE* GrGLShaderSourceProc)(GrGLuint shader, GrGLsizei count, const char** str, const GrGLint* length);
+#endif
typedef GrGLvoid (GR_GL_FUNCTION_TYPE* GrGLStencilFuncProc)(GrGLenum func, GrGLint ref, GrGLuint mask);
typedef GrGLvoid (GR_GL_FUNCTION_TYPE* GrGLStencilFuncSeparateProc)(GrGLenum face, GrGLenum func, GrGLint ref, GrGLuint mask);
typedef GrGLvoid (GR_GL_FUNCTION_TYPE* GrGLStencilMaskProc)(GrGLuint mask);
diff --git a/include/gpu/gl/SkNativeGLContext.h b/include/gpu/gl/SkNativeGLContext.h
index 52118da86e..93744e7ec7 100644
--- a/include/gpu/gl/SkNativeGLContext.h
+++ b/include/gpu/gl/SkNativeGLContext.h
@@ -12,7 +12,7 @@
#if defined(SK_BUILD_FOR_MAC)
#include <AGL/agl.h>
-#elif defined(SK_BUILD_FOR_ANDROID)
+#elif defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_NACL)
#include <GLES2/gl2.h>
#include <EGL/egl.h>
#elif defined(SK_BUILD_FOR_UNIX)
@@ -39,6 +39,10 @@ public:
private:
#if defined(SK_BUILD_FOR_MAC)
AGLContext fOldAGLContext;
+ #elif defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_NACL)
+ EGLContext fOldEGLContext;
+ EGLDisplay fOldDisplay;
+ EGLSurface fOldSurface;
#elif defined(SK_BUILD_FOR_UNIX)
GLXContext fOldGLXContext;
Display* fOldDisplay;
@@ -46,10 +50,7 @@ public:
#elif defined(SK_BUILD_FOR_WIN32)
HDC fOldHDC;
HGLRC fOldHGLRC;
- #elif defined(SK_BUILD_FOR_ANDROID)
- EGLContext fOldEGLContext;
- EGLDisplay fOldDisplay;
- EGLSurface fOldSurface;
+
#elif defined(SK_BUILD_FOR_IOS)
void* fEAGLContext;
#endif
@@ -62,6 +63,10 @@ protected:
private:
#if defined(SK_BUILD_FOR_MAC)
AGLContext fContext;
+#elif defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_NACL)
+ EGLContext fContext;
+ EGLDisplay fDisplay;
+ EGLSurface fSurface;
#elif defined(SK_BUILD_FOR_UNIX)
GLXContext fContext;
Display* fDisplay;
@@ -72,10 +77,6 @@ private:
HDC fDeviceContext;
HGLRC fGlRenderContext;
static ATOM gWC;
-#elif defined(SK_BUILD_FOR_ANDROID)
- EGLContext fContext;
- EGLDisplay fDisplay;
- EGLSurface fSurface;
#elif defined(SK_BUILD_FOR_IOS)
void* fEAGLContext;
#endif
diff --git a/include/images/SkBitmapFactory.h b/include/images/SkBitmapFactory.h
new file mode 100644
index 0000000000..6779dc24c3
--- /dev/null
+++ b/include/images/SkBitmapFactory.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkBitmapFactory_DEFINED
+#define SkBitmapFactory_DEFINED
+
+class SkBitmap;
+class SkData;
+
+/**
+ * General purpose factory for decoding bitmaps.
+ *
+ * Currently only provides a way to decode a bitmap or its dimensions from an SkData. Future plans
+ * include options to provide a bitmap which caches the pixel data.
+ */
+class SkBitmapFactory {
+
+public:
+ enum Constraints {
+ /**
+ * Only decode the bounds of the bitmap. No pixels will be allocated.
+ */
+ kDecodeBoundsOnly_Constraint,
+
+ /**
+ * Decode the bounds and pixels of the bitmap.
+ */
+ kDecodePixels_Constraint,
+ };
+
+ /**
+ * Decodes an SkData into an SkBitmap.
+ * @param SkBitmap Already created bitmap to encode into.
+ * @param SkData Encoded SkBitmap data.
+ * @param constraint Specifications for how to do the decoding.
+ * @return True on success. If false, passed in SkBitmap is unmodified.
+ */
+ static bool DecodeBitmap(SkBitmap*, const SkData*,
+ Constraints constraint = kDecodePixels_Constraint);
+};
+
+#endif // SkBitmapFactory_DEFINED
diff --git a/include/images/SkFlipPixelRef.h b/include/images/SkFlipPixelRef.h
index ac437805d6..3443e3d5b7 100644
--- a/include/images/SkFlipPixelRef.h
+++ b/include/images/SkFlipPixelRef.h
@@ -21,7 +21,7 @@ class SkFlipPixelRef : public SkPixelRef {
public:
SkFlipPixelRef(SkBitmap::Config, int width, int height);
virtual ~SkFlipPixelRef();
-
+
bool isDirty() const { return fFlipper.isDirty(); }
const SkRegion& dirtyRgn() const { return fFlipper.dirtyRgn(); }
@@ -32,16 +32,8 @@ public:
const SkRegion& beginUpdate(SkBitmap* device);
void endUpdate();
-
- SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkFlipPixelRef)
-
-protected:
- virtual void* onLockPixels(SkColorTable**);
- virtual void onUnlockPixels();
-
- SkFlipPixelRef(SkFlattenableReadBuffer&);
- virtual void flatten(SkFlattenableWriteBuffer&) const SK_OVERRIDE;
-
+
+ SK_DECLARE_UNFLATTENABLE_OBJECT()
private:
void getFrontBack(const void** front, void** back) const {
if (front) {
@@ -59,9 +51,14 @@ private:
static void CopyBitsFromAddr(const SkBitmap& dst, const SkRegion& clip,
const void* srcAddr);
+protected:
+ virtual void* onLockPixels(SkColorTable**);
+ virtual void onUnlockPixels();
+
+private:
SkMutex fMutex;
SkPageFlipper fFlipper;
-
+
void* fStorage;
void* fPage0; // points into fStorage;
void* fPage1; // points into fStorage;
@@ -81,10 +78,10 @@ public:
fRef->endUpdate();
}
}
-
+
const SkBitmap& bitmap() const { return fBitmap; }
const SkRegion& dirty() const { return *fDirty; }
-
+
// optional. This gets automatically called in the destructor (only once)
void endUpdate() {
if (fRef) {
diff --git a/include/images/SkImageEncoder.h b/include/images/SkImageEncoder.h
index 076778d2e1..d4d7169565 100644
--- a/include/images/SkImageEncoder.h
+++ b/include/images/SkImageEncoder.h
@@ -29,8 +29,25 @@ public:
kDefaultQuality = 80
};
- bool encodeFile(const char file[], const SkBitmap&, int quality);
- bool encodeStream(SkWStream*, const SkBitmap&, int quality);
+ /**
+ * Encode bitmap 'bm' in the desired format, writing results to
+ * file 'file', at quality level 'quality' (which can be in range
+ * 0-100).
+ *
+ * Calls the particular implementation's onEncode() method to
+ * actually do the encoding.
+ */
+ bool encodeFile(const char file[], const SkBitmap& bm, int quality);
+
+ /**
+ * Encode bitmap 'bm' in the desired format, writing results to
+ * stream 'stream', at quality level 'quality' (which can be in
+ * range 0-100).
+ *
+ * Calls the particular implementation's onEncode() method to
+ * actually do the encoding.
+ */
+ bool encodeStream(SkWStream* stream, const SkBitmap& bm, int quality);
static bool EncodeFile(const char file[], const SkBitmap&, Type,
int quality);
@@ -38,7 +55,14 @@ public:
int quality);
protected:
- virtual bool onEncode(SkWStream*, const SkBitmap&, int quality) = 0;
+ /**
+ * Encode bitmap 'bm' in the desired format, writing results to
+ * stream 'stream', at quality level 'quality' (which can be in
+ * range 0-100).
+ *
+ * This must be overridden by each SkImageEncoder implementation.
+ */
+ virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) = 0;
};
// This macro declares a global (i.e., non-class owned) creation entry point
diff --git a/include/utils/SkCondVar.h b/include/utils/SkCondVar.h
new file mode 100644
index 0000000000..15f16e662f
--- /dev/null
+++ b/include/utils/SkCondVar.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkCondVar_DEFINED
+#define SkCondVar_DEFINED
+
+#ifdef SK_USE_POSIX_THREADS
+#include <pthread.h>
+#elif defined(SK_BUILD_FOR_WIN32)
+#include <Windows.h>
+#endif
+
+/**
+ * Condition variable for blocking access to shared data from other threads and
+ * controlling which threads are awake.
+ *
+ * Currently only supported on platforms with posix threads and Windows Vista and
+ * above.
+ */
+class SkCondVar {
+public:
+ SkCondVar();
+ ~SkCondVar();
+
+ /**
+ * Lock a mutex. Must be done before calling the other functions on this object.
+ */
+ void lock();
+
+ /**
+ * Unlock the mutex.
+ */
+ void unlock();
+
+ /**
+ * Pause the calling thread. Will be awoken when signal() or broadcast() is called.
+ * Must be called while lock() is held (but gives it up while waiting). Once awoken,
+ * the calling thread will hold the lock once again.
+ */
+ void wait();
+
+ /**
+ * Wake one thread waiting on this condition. Must be called while lock()
+ * is held.
+ */
+ void signal();
+
+ /**
+ * Wake all threads waiting on this condition. Must be called while lock()
+ * is held.
+ */
+ void broadcast();
+
+private:
+#ifdef SK_USE_POSIX_THREADS
+ pthread_mutex_t fMutex;
+ pthread_cond_t fCond;
+#elif defined(SK_BUILD_FOR_WIN32)
+ CRITICAL_SECTION fCriticalSection;
+ CONDITION_VARIABLE fCondition;
+#endif
+};
+
+#endif
diff --git a/include/utils/SkCountdown.h b/include/utils/SkCountdown.h
new file mode 100644
index 0000000000..6bcec7d5ff
--- /dev/null
+++ b/include/utils/SkCountdown.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkCountdown_DEFINED
+#define SkCountdown_DEFINED
+
+#include "SkCondVar.h"
+#include "SkRunnable.h"
+#include "SkTypes.h"
+
+class SkCountdown : public SkRunnable {
+public:
+ explicit SkCountdown(int32_t count);
+
+ /**
+ * Resets the countdown to the count provided.
+ */
+ void reset(int32_t count);
+
+ virtual void run() SK_OVERRIDE;
+
+ /**
+ * Blocks until run() has been called count times.
+ */
+ void wait();
+
+private:
+ SkCondVar fReady;
+ int32_t fCount;
+};
+
+#endif
diff --git a/include/utils/SkDeferredCanvas.h b/include/utils/SkDeferredCanvas.h
index 96d1fca08d..1d15c6f9d0 100644
--- a/include/utils/SkDeferredCanvas.h
+++ b/include/utils/SkDeferredCanvas.h
@@ -116,6 +116,12 @@ public:
size_t freeMemoryIfPossible(size_t bytesToFree);
/**
+ * Specifies the maximum size (in bytes) allowed for a given image to be
+ * rendered using the deferred canvas.
+ */
+ void setBitmapSizeThreshold(size_t sizeThreshold);
+
+ /**
* Executes all pending commands without drawing
*/
void silentFlush();
@@ -134,6 +140,8 @@ public:
virtual void setMatrix(const SkMatrix& matrix) SK_OVERRIDE;
virtual bool clipRect(const SkRect& rect, SkRegion::Op op,
bool doAntiAlias) SK_OVERRIDE;
+ virtual bool clipRRect(const SkRRect& rect, SkRegion::Op op,
+ bool doAntiAlias) SK_OVERRIDE;
virtual bool clipPath(const SkPath& path, SkRegion::Op op,
bool doAntiAlias) SK_OVERRIDE;
virtual bool clipRegion(const SkRegion& deviceRgn,
@@ -142,8 +150,9 @@ public:
virtual void drawPaint(const SkPaint& paint) SK_OVERRIDE;
virtual void drawPoints(PointMode mode, size_t count, const SkPoint pts[],
const SkPaint& paint) SK_OVERRIDE;
- virtual void drawRect(const SkRect& rect, const SkPaint& paint)
- SK_OVERRIDE;
+ virtual void drawOval(const SkRect&, const SkPaint& paint) SK_OVERRIDE;
+ virtual void drawRect(const SkRect& rect, const SkPaint& paint) SK_OVERRIDE;
+ virtual void drawRRect(const SkRRect&, const SkPaint& paint) SK_OVERRIDE;
virtual void drawPath(const SkPath& path, const SkPaint& paint)
SK_OVERRIDE;
virtual void drawBitmap(const SkBitmap& bitmap, SkScalar left,
diff --git a/include/utils/SkDumpCanvas.h b/include/utils/SkDumpCanvas.h
index 4eb1f25fcc..608ab01334 100644
--- a/include/utils/SkDumpCanvas.h
+++ b/include/utils/SkDumpCanvas.h
@@ -35,7 +35,9 @@ public:
kDrawPaint_Verb,
kDrawPoints_Verb,
+ kDrawOval_Verb,
kDrawRect_Verb,
+ kDrawRRect_Verb,
kDrawPath_Verb,
kDrawBitmap_Verb,
kDrawText_Verb,
@@ -76,6 +78,7 @@ public:
virtual void setMatrix(const SkMatrix& matrix) SK_OVERRIDE;
virtual bool clipRect(const SkRect&, SkRegion::Op, bool) SK_OVERRIDE;
+ virtual bool clipRRect(const SkRRect&, SkRegion::Op, bool) SK_OVERRIDE;
virtual bool clipPath(const SkPath&, SkRegion::Op, bool) SK_OVERRIDE;
virtual bool clipRegion(const SkRegion& deviceRgn,
SkRegion::Op) SK_OVERRIDE;
@@ -83,7 +86,9 @@ public:
virtual void drawPaint(const SkPaint& paint) SK_OVERRIDE;
virtual void drawPoints(PointMode mode, size_t count, const SkPoint pts[],
const SkPaint& paint) SK_OVERRIDE;
- virtual void drawRect(const SkRect& rect, const SkPaint& paint) SK_OVERRIDE;
+ virtual void drawOval(const SkRect&, const SkPaint& paint) SK_OVERRIDE;
+ virtual void drawRect(const SkRect&, const SkPaint& paint) SK_OVERRIDE;
+ virtual void drawRRect(const SkRRect&, const SkPaint& paint) SK_OVERRIDE;
virtual void drawPath(const SkPath& path, const SkPaint& paint) SK_OVERRIDE;
virtual void drawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar top,
const SkPaint* paint) SK_OVERRIDE;
diff --git a/include/utils/SkMatrix44.h b/include/utils/SkMatrix44.h
index 93140b022d..4789e7e209 100644
--- a/include/utils/SkMatrix44.h
+++ b/include/utils/SkMatrix44.h
@@ -1,4 +1,3 @@
-
/*
* Copyright 2011 Google Inc.
*
@@ -6,8 +5,6 @@
* found in the LICENSE file.
*/
-
-
#ifndef SkMatrix44_DEFINED
#define SkMatrix44_DEFINED
@@ -15,7 +12,12 @@
#include "SkScalar.h"
#ifdef SK_MSCALAR_IS_DOUBLE
+#ifdef SK_MSCALAR_IS_FLOAT
+ #error "can't define MSCALAR both as DOUBLE and FLOAT"
+#endif
typedef double SkMScalar;
+ typedef int64_t SkMIntScalar;
+
static inline double SkFloatToMScalar(float x) {
return static_cast<double>(x);
}
@@ -29,8 +31,13 @@
return x;
}
static const SkMScalar SK_MScalarPI = 3.141592653589793;
-#else
+#elif defined SK_MSCALAR_IS_FLOAT
+#ifdef SK_MSCALAR_IS_DOUBLE
+ #error "can't define MSCALAR both as DOUBLE and FLOAT"
+#endif
typedef float SkMScalar;
+ typedef int32_t SkMIntScalar;
+
static inline float SkFloatToMScalar(float x) {
return x;
}
@@ -46,19 +53,8 @@
static const SkMScalar SK_MScalarPI = 3.14159265f;
#endif
-#ifdef SK_SCALAR_IS_FLOAT
- #define SkMScalarToScalar SkMScalarToFloat
- #define SkScalarToMScalar SkFloatToMScalar
-#else
- #if SK_MSCALAR_IS_DOUBLE
- // we don't have fixed <-> double macros, use double<->scalar macros
- #define SkMScalarToScalar SkDoubleToScalar
- #define SkScalarToMScalar SkScalarToDouble
- #else
- #define SkMScalarToScalar SkFloatToFixed
- #define SkScalarToMScalar SkFixedToFloat
- #endif
-#endif
+#define SkMScalarToScalar SkMScalarToFloat
+#define SkScalarToMScalar SkFloatToMScalar
static const SkMScalar SK_MScalar1 = 1;
@@ -107,37 +103,147 @@ struct SkVector4 {
class SK_API SkMatrix44 {
public:
- SkMatrix44();
+
+ enum Uninitialized_Constructor {
+ kUninitialized_Constructor
+ };
+ enum Identity_Constructor {
+ kIdentity_Constructor
+ };
+
+ SkMatrix44(Uninitialized_Constructor) { }
+ SkMatrix44(Identity_Constructor) { this->setIdentity(); }
+
+ SkMatrix44() { this->setIdentity(); }
SkMatrix44(const SkMatrix44&);
SkMatrix44(const SkMatrix44& a, const SkMatrix44& b);
SkMatrix44& operator=(const SkMatrix44& src) {
+ SkASSERT(sizeof(src) == sizeof(fMat) + sizeof(SkMIntScalar));
memcpy(this, &src, sizeof(*this));
return *this;
}
- bool operator==(const SkMatrix44& other) const {
- return !memcmp(this, &other, sizeof(*this));
- }
+ bool operator==(const SkMatrix44& other) const;
bool operator!=(const SkMatrix44& other) const {
- return !!memcmp(this, &other, sizeof(*this));
+ return !(other == *this);
}
SkMatrix44(const SkMatrix&);
SkMatrix44& operator=(const SkMatrix& src);
operator SkMatrix() const;
- SkMScalar get(int row, int col) const;
- void set(int row, int col, const SkMScalar& value);
+ /**
+ * Return a reference to a const identity matrix
+ */
+ static const SkMatrix44& I();
+
+ enum TypeMask {
+ kIdentity_Mask = 0,
+ kTranslate_Mask = 0x01, //!< set if the matrix has translation
+ kScale_Mask = 0x02, //!< set if the matrix has any scale != 1
+ kAffine_Mask = 0x04, //!< set if the matrix skews or rotates
+ kPerspective_Mask = 0x08 //!< set if the matrix is in perspective
+ };
+
+ /**
+ * Returns a bitfield describing the transformations the matrix may
+ * perform. The bitfield is computed conservatively, so it may include
+ * false positives. For example, when kPerspective_Mask is true, all
+ * other bits may be set to true even in the case of a pure perspective
+ * transform.
+ */
+ inline TypeMask getType() const {
+ if (fTypeMask & kUnknown_Mask) {
+ fTypeMask = this->computeTypeMask();
+ }
+ SkASSERT(!(fTypeMask & kUnknown_Mask));
+ return (TypeMask)fTypeMask;
+ }
+
+ /**
+ * Return true if the matrix is identity.
+ */
+ inline bool isIdentity() const {
+ return kIdentity_Mask == this->getType();
+ }
+
+ /**
+ * Return true if the matrix contains translate or is identity.
+ */
+ inline bool isTranslate() const {
+ return !(this->getType() & ~kTranslate_Mask);
+ }
+
+ /**
+ * Return true if the matrix only contains scale or translate or is identity.
+ */
+ inline bool isScaleTranslate() const {
+ return !(this->getType() & ~(kScale_Mask | kTranslate_Mask));
+ }
+
+ void setIdentity();
+ inline void reset() { this->setIdentity();}
+
+ /**
+ * get a value from the matrix. The row,col parameters work as follows:
+ * (0, 0) scale-x
+ * (0, 3) translate-x
+ * (3, 0) perspective-x
+ */
+ inline SkMScalar get(int row, int col) const {
+ SkASSERT((unsigned)row <= 3);
+ SkASSERT((unsigned)col <= 3);
+ return fMat[col][row];
+ }
+
+ /**
+ * set a value in the matrix. The row,col parameters work as follows:
+ * (0, 0) scale-x
+ * (0, 3) translate-x
+ * (3, 0) perspective-x
+ */
+ inline void set(int row, int col, SkMScalar value) {
+ SkASSERT((unsigned)row <= 3);
+ SkASSERT((unsigned)col <= 3);
+ fMat[col][row] = value;
+ this->dirtyTypeMask();
+ }
+
+ inline double getDouble(int row, int col) const {
+ return SkMScalarToDouble(this->get(row, col));
+ }
+ inline void setDouble(int row, int col, double value) {
+ this->set(row, col, SkDoubleToMScalar(value));
+ }
+ /** These methods allow one to efficiently read matrix entries into an
+ * array. The given array must have room for exactly 16 entries. Whenever
+ * possible, they will try to use memcpy rather than an entry-by-entry
+ * copy.
+ */
void asColMajorf(float[]) const;
void asColMajord(double[]) const;
void asRowMajorf(float[]) const;
void asRowMajord(double[]) const;
- bool isIdentity() const;
- void setIdentity();
- void reset() { this->setIdentity();}
+ /** These methods allow one to efficiently set all matrix entries from an
+ * array. The given array must have room for exactly 16 entries. Whenever
+ * possible, they will try to use memcpy rather than an entry-by-entry
+ * copy.
+ */
+ void setColMajorf(const float[]);
+ void setColMajord(const double[]);
+ void setRowMajorf(const float[]);
+ void setRowMajord(const double[]);
+
+#ifdef SK_MSCALAR_IS_FLOAT
+ void setColMajor(const SkMScalar data[]) { this->setColMajorf(data); }
+ void setRowMajor(const SkMScalar data[]) { this->setRowMajorf(data); }
+#else
+ void setColMajor(const SkMScalar data[]) { this->setColMajord(data); }
+ void setRowMajor(const SkMScalar data[]) { this->setRowMajord(data); }
+#endif
void set3x3(SkMScalar m00, SkMScalar m01, SkMScalar m02,
SkMScalar m10, SkMScalar m11, SkMScalar m12,
@@ -151,13 +257,13 @@ public:
void preScale(SkMScalar sx, SkMScalar sy, SkMScalar sz);
void postScale(SkMScalar sx, SkMScalar sy, SkMScalar sz);
- void setScale(SkMScalar scale) {
+ inline void setScale(SkMScalar scale) {
this->setScale(scale, scale, scale);
}
- void preScale(SkMScalar scale) {
+ inline void preScale(SkMScalar scale) {
this->preScale(scale, scale, scale);
}
- void postScale(SkMScalar scale) {
+ inline void postScale(SkMScalar scale) {
this->postScale(scale, scale, scale);
}
@@ -178,10 +284,10 @@ public:
SkMScalar radians);
void setConcat(const SkMatrix44& a, const SkMatrix44& b);
- void preConcat(const SkMatrix44& m) {
+ inline void preConcat(const SkMatrix44& m) {
this->setConcat(*this, m);
}
- void postConcat(const SkMatrix44& m) {
+ inline void postConcat(const SkMatrix44& m) {
this->setConcat(m, *this);
}
@@ -194,12 +300,35 @@ public:
*/
bool invert(SkMatrix44* inverse) const;
+ /** Transpose this matrix in place. */
+ void transpose();
+
/** Apply the matrix to the src vector, returning the new vector in dst.
It is legal for src and dst to point to the same memory.
*/
- void map(const SkScalar src[4], SkScalar dst[4]) const;
+ void mapScalars(const SkScalar src[4], SkScalar dst[4]) const;
+ inline void mapScalars(SkScalar vec[4]) const {
+ this->mapScalars(vec, vec);
+ }
+
+ // DEPRECATED: call mapScalars()
+ void map(const SkScalar src[4], SkScalar dst[4]) const {
+ this->mapScalars(src, dst);
+ }
+ // DEPRECATED: call mapScalars()
void map(SkScalar vec[4]) const {
- this->map(vec, vec);
+ this->mapScalars(vec, vec);
+ }
+
+#ifdef SK_MSCALAR_IS_DOUBLE
+ void mapMScalars(const SkMScalar src[4], SkMScalar dst[4]) const;
+#elif defined SK_MSCALAR_IS_FLOAT
+ inline void mapMScalars(const SkMScalar src[4], SkMScalar dst[4]) const {
+ this->mapScalars(src, dst);
+ }
+#endif
+ inline void mapMScalars(SkMScalar vec[4]) const {
+ this->mapMScalars(vec, vec);
}
friend SkVector4 operator*(const SkMatrix44& m, const SkVector4& src) {
@@ -208,17 +337,64 @@ public:
return dst;
}
+ /**
+ * map an array of [x, y, 0, 1] through the matrix, returning an array
+ * of [x', y', z', w'].
+ *
+ * @param src2 array of [x, y] pairs, with implied z=0 and w=1
+ * @param count number of [x, y] pairs in src2
+ * @param dst4 array of [x', y', z', w'] quads as the output.
+ */
+ void map2(const float src2[], int count, float dst4[]) const;
+ void map2(const double src2[], int count, double dst4[]) const;
+
void dump() const;
+ double determinant() const;
+
private:
- /* Stored in the same order as opengl:
- [3][0] = tx
- [3][1] = ty
- [3][2] = tz
- */
- SkMScalar fMat[4][4];
+ SkMScalar fMat[4][4];
+ // we use SkMIntScalar instead of just int, as we want to ensure that
+ // we are always packed with no extra bits, allowing us to call memcpy
+ // without fear of copying uninitialized bits.
+ mutable SkMIntScalar fTypeMask;
- double determinant() const;
+ enum {
+ kUnknown_Mask = 0x80,
+
+ kAllPublic_Masks = 0xF
+ };
+
+ SkMScalar transX() const { return fMat[3][0]; }
+ SkMScalar transY() const { return fMat[3][1]; }
+ SkMScalar transZ() const { return fMat[3][2]; }
+
+ SkMScalar scaleX() const { return fMat[0][0]; }
+ SkMScalar scaleY() const { return fMat[1][1]; }
+ SkMScalar scaleZ() const { return fMat[2][2]; }
+
+ SkMScalar perspX() const { return fMat[0][3]; }
+ SkMScalar perspY() const { return fMat[1][3]; }
+ SkMScalar perspZ() const { return fMat[2][3]; }
+
+ int computeTypeMask() const;
+
+ inline void dirtyTypeMask() {
+ fTypeMask = kUnknown_Mask;
+ }
+
+ inline void setTypeMask(int mask) {
+ SkASSERT(0 == (~(kAllPublic_Masks | kUnknown_Mask) & mask));
+ fTypeMask = mask;
+ }
+
+ /**
+ * Does not take the time to 'compute' the typemask. Only returns true if
+ * we already know that this matrix is identity.
+ */
+ inline bool isTriviallyIdentity() const {
+ return 0 == fTypeMask;
+ }
};
#endif
diff --git a/include/utils/SkNWayCanvas.h b/include/utils/SkNWayCanvas.h
index 065adf0a47..fcd0ccff0e 100644
--- a/include/utils/SkNWayCanvas.h
+++ b/include/utils/SkNWayCanvas.h
@@ -34,6 +34,7 @@ public:
virtual bool concat(const SkMatrix& matrix) SK_OVERRIDE;
virtual void setMatrix(const SkMatrix& matrix) SK_OVERRIDE;
virtual bool clipRect(const SkRect&, SkRegion::Op, bool) SK_OVERRIDE;
+ virtual bool clipRRect(const SkRRect&, SkRegion::Op, bool) SK_OVERRIDE;
virtual bool clipPath(const SkPath&, SkRegion::Op, bool) SK_OVERRIDE;
virtual bool clipRegion(const SkRegion& deviceRgn,
SkRegion::Op) SK_OVERRIDE;
@@ -41,7 +42,9 @@ public:
virtual void drawPaint(const SkPaint& paint) SK_OVERRIDE;
virtual void drawPoints(PointMode mode, size_t count, const SkPoint pts[],
const SkPaint&) SK_OVERRIDE;
- virtual void drawRect(const SkRect& rect, const SkPaint&) SK_OVERRIDE;
+ virtual void drawOval(const SkRect&, const SkPaint&) SK_OVERRIDE;
+ virtual void drawRect(const SkRect&, const SkPaint&) SK_OVERRIDE;
+ virtual void drawRRect(const SkRRect&, const SkPaint&) SK_OVERRIDE;
virtual void drawPath(const SkPath& path, const SkPaint&) SK_OVERRIDE;
virtual void drawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar top,
const SkPaint*) SK_OVERRIDE;
diff --git a/include/utils/SkPictureUtils.h b/include/utils/SkPictureUtils.h
new file mode 100644
index 0000000000..5e6b051f45
--- /dev/null
+++ b/include/utils/SkPictureUtils.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkPictureUtils_DEFINED
+#define SkPictureUtils_DEFINED
+
+#include "SkPicture.h"
+
+class SkData;
+struct SkRect;
+
+class SK_API SkPictureUtils {
+public:
+ /**
+ * Given a rectangular visible "window" into the picture, return an array
+ * of SkPixelRefs that might intersect that area. To keep the call fast,
+ * the returned list is not guaranteed to be exact, so it may miss some,
+ * and it may return false positives.
+ *
+ * The pixelrefs returned in the SkData are already owned by the picture,
+ * so the returned pointers are only valid while the picture is in scope
+ * and remains unchanged.
+ */
+ static SkData* GatherPixelRefs(SkPicture* pict, const SkRect& area);
+};
+
+#endif
diff --git a/include/utils/SkProxyCanvas.h b/include/utils/SkProxyCanvas.h
index aa4708544f..bd260c77d2 100644
--- a/include/utils/SkProxyCanvas.h
+++ b/include/utils/SkProxyCanvas.h
@@ -39,6 +39,7 @@ public:
virtual void setMatrix(const SkMatrix& matrix) SK_OVERRIDE;
virtual bool clipRect(const SkRect&, SkRegion::Op, bool) SK_OVERRIDE;
+ virtual bool clipRRect(const SkRRect&, SkRegion::Op, bool) SK_OVERRIDE;
virtual bool clipPath(const SkPath&, SkRegion::Op, bool) SK_OVERRIDE;
virtual bool clipRegion(const SkRegion& deviceRgn,
SkRegion::Op op = SkRegion::kIntersect_Op) SK_OVERRIDE;
@@ -46,7 +47,9 @@ public:
virtual void drawPaint(const SkPaint& paint) SK_OVERRIDE;
virtual void drawPoints(PointMode mode, size_t count, const SkPoint pts[],
const SkPaint& paint) SK_OVERRIDE;
- virtual void drawRect(const SkRect& rect, const SkPaint& paint) SK_OVERRIDE;
+ virtual void drawOval(const SkRect&, const SkPaint& paint) SK_OVERRIDE;
+ virtual void drawRect(const SkRect&, const SkPaint& paint) SK_OVERRIDE;
+ virtual void drawRRect(const SkRRect&, const SkPaint& paint) SK_OVERRIDE;
virtual void drawPath(const SkPath& path, const SkPaint& paint) SK_OVERRIDE;
virtual void drawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar top,
const SkPaint* paint = NULL) SK_OVERRIDE;
diff --git a/include/utils/SkRunnable.h b/include/utils/SkRunnable.h
new file mode 100644
index 0000000000..84e43750f6
--- /dev/null
+++ b/include/utils/SkRunnable.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkRunnable_DEFINED
+#define SkRunnable_DEFINED
+
+class SkRunnable {
+public:
+ virtual ~SkRunnable() {};
+ virtual void run() = 0;
+};
+
+#endif
diff --git a/include/utils/SkThreadPool.h b/include/utils/SkThreadPool.h
new file mode 100644
index 0000000000..cc45fc2cd5
--- /dev/null
+++ b/include/utils/SkThreadPool.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkThreadPool_DEFINED
+#define SkThreadPool_DEFINED
+
+#include "SkCondVar.h"
+#include "SkTDArray.h"
+#include "SkTInternalLList.h"
+
+class SkRunnable;
+class SkThread;
+
+class SkThreadPool {
+
+public:
+ /**
+ * Create a threadpool with exactly count (>=0) threads.
+ */
+ explicit SkThreadPool(int count);
+ ~SkThreadPool();
+
+ /**
+ * Queues up an SkRunnable to run when a thread is available, or immediately if
+ * count is 0. NULL is a safe no-op. Does not take ownership.
+ */
+ void add(SkRunnable*);
+
+ private:
+ struct LinkedRunnable {
+ // Unowned pointer.
+ SkRunnable* fRunnable;
+
+ private:
+ SK_DECLARE_INTERNAL_LLIST_INTERFACE(LinkedRunnable);
+ };
+
+ SkTInternalLList<LinkedRunnable> fQueue;
+ SkCondVar fReady;
+ SkTDArray<SkThread*> fThreads;
+ bool fDone;
+
+ static void Loop(void*); // Static because we pass in this.
+};
+
+#endif
diff --git a/include/views/SkOSWindow_NaCl.h b/include/views/SkOSWindow_NaCl.h
new file mode 100644
index 0000000000..8c4ddc6083
--- /dev/null
+++ b/include/views/SkOSWindow_NaCl.h
@@ -0,0 +1,46 @@
+
+/*
+ * Copyright 2012 Skia
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#ifndef SkOSWindow_NaCl_DEFINED
+#define SkOSWindow_NaCl_DEFINED
+
+#include "SkWindow.h"
+
+class SkIRect;
+
+class SkOSWindow : public SkWindow {
+public:
+ SkOSWindow(void*) {}
+ ~SkOSWindow() {}
+
+ enum SkBackEndTypes {
+ kNone_BackEndType,
+ kNativeGL_BackEndType,
+ };
+
+ bool attach(SkBackEndTypes /* attachType */, int /* msaaSampleCount */) {
+ return true;
+ }
+ void detach() {}
+ void present() {}
+
+ virtual void onPDFSaved(const char title[], const char desc[],
+ const char path[]);
+
+protected:
+ // overrides from SkWindow
+ virtual void onHandleInval(const SkIRect&);
+ virtual void onSetTitle(const char title[]);
+
+private:
+ typedef SkWindow INHERITED;
+};
+
+#endif
+
diff --git a/include/views/SkWindow.h b/include/views/SkWindow.h
index f1d3881fe3..ca48e27114 100644
--- a/include/views/SkWindow.h
+++ b/include/views/SkWindow.h
@@ -102,7 +102,9 @@ private:
////////////////////////////////////////////////////////////////////////////////
-#if defined(SK_BUILD_FOR_MAC)
+#if defined(SK_BUILD_FOR_NACL)
+ #include "SkOSWindow_NaCl.h"
+#elif defined(SK_BUILD_FOR_MAC)
#include "SkOSWindow_Mac.h"
#elif defined(SK_BUILD_FOR_WIN)
#include "SkOSWindow_Win.h"
diff --git a/samplecode/ClockFaceView.cpp b/samplecode/ClockFaceView.cpp
new file mode 100644
index 0000000000..f99a6a1c71
--- /dev/null
+++ b/samplecode/ClockFaceView.cpp
@@ -0,0 +1,257 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SampleCode.h"
+#include "SkView.h"
+#include "SkCanvas.h"
+#include "SkFlattenableBuffers.h"
+#include "SkGradientShader.h"
+#include "SkPath.h"
+#include "SkRegion.h"
+#include "SkShader.h"
+#include "SkUtils.h"
+#include "SkColorPriv.h"
+#include "SkColorFilter.h"
+#include "SkTypeface.h"
+#include "SkAvoidXfermode.h"
+
+static inline SkPMColor rgb2gray(SkPMColor c)
+{
+ unsigned r = SkGetPackedR32(c);
+ unsigned g = SkGetPackedG32(c);
+ unsigned b = SkGetPackedB32(c);
+
+ unsigned x = (r * 5 + g * 7 + b * 4) >> 4;
+
+ return SkPackARGB32(0, x, x, x) | (c & (SK_A32_MASK << SK_A32_SHIFT));
+}
+
+class SkGrayScaleColorFilter : public SkColorFilter {
+public:
+ virtual void filterSpan(const SkPMColor src[], int count, SkPMColor result[])
+ {
+ for (int i = 0; i < count; i++)
+ result[i] = rgb2gray(src[i]);
+ }
+};
+
+class SkChannelMaskColorFilter : public SkColorFilter {
+public:
+ SkChannelMaskColorFilter(U8CPU redMask, U8CPU greenMask, U8CPU blueMask)
+ {
+ fMask = SkPackARGB32(0xFF, redMask, greenMask, blueMask);
+ }
+
+ virtual void filterSpan(const SkPMColor src[], int count, SkPMColor result[])
+ {
+ SkPMColor mask = fMask;
+ for (int i = 0; i < count; i++)
+ result[i] = src[i] & mask;
+ }
+
+private:
+ SkPMColor fMask;
+};
+
+///////////////////////////////////////////////////////////
+
+#include "SkGradientShader.h"
+#include "SkLayerRasterizer.h"
+#include "SkBlurMaskFilter.h"
+
+#include "Sk2DPathEffect.h"
+
+class Dot2DPathEffect : public Sk2DPathEffect {
+public:
+ Dot2DPathEffect(SkScalar radius, const SkMatrix& matrix,
+ SkTDArray<SkPoint>* pts)
+ : Sk2DPathEffect(matrix), fRadius(radius), fPts(pts) {}
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Dot2DPathEffect)
+
+protected:
+ virtual void begin(const SkIRect& uvBounds, SkPath* dst) const SK_OVERRIDE {
+ if (fPts) {
+ fPts->reset();
+ }
+ this->INHERITED::begin(uvBounds, dst);
+ }
+
+ virtual void next(const SkPoint& loc, int u, int v,
+ SkPath* dst) const SK_OVERRIDE {
+ if (fPts) {
+ *fPts->append() = loc;
+ }
+ dst->addCircle(loc.fX, loc.fY, fRadius);
+ }
+
+ Dot2DPathEffect(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fRadius = buffer.readScalar();
+ fPts = NULL;
+ }
+
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
+ this->INHERITED::flatten(buffer);
+ buffer.writeScalar(fRadius);
+ }
+
+private:
+ SkScalar fRadius;
+ SkTDArray<SkPoint>* fPts;
+
+ typedef Sk2DPathEffect INHERITED;
+};
+
+class InverseFillPE : public SkPathEffect {
+public:
+ InverseFillPE() {}
+ virtual bool filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec*) const SK_OVERRIDE {
+ *dst = src;
+ dst->setFillType(SkPath::kInverseWinding_FillType);
+ return true;
+ }
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(InverseFillPE)
+
+protected:
+ InverseFillPE(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {}
+private:
+
+ typedef SkPathEffect INHERITED;
+};
+
+static SkPathEffect* makepe(float interp, SkTDArray<SkPoint>* pts) {
+ SkMatrix lattice;
+ SkScalar rad = 3 + SkIntToScalar(4) * (1 - interp);
+ lattice.setScale(rad*2, rad*2, 0, 0);
+ lattice.postSkew(SK_Scalar1/3, 0, 0, 0);
+ return new Dot2DPathEffect(rad, lattice, pts);
+}
+
+static void r7(SkLayerRasterizer* rast, SkPaint& p, SkScalar interp) {
+ p.setPathEffect(makepe(SkScalarToFloat(interp), NULL))->unref();
+ rast->addLayer(p);
+#if 0
+ p.setPathEffect(new InverseFillPE())->unref();
+ p.setXfermodeMode(SkXfermode::kSrcIn_Mode);
+ p.setXfermodeMode(SkXfermode::kClear_Mode);
+ p.setAlpha((1 - interp) * 255);
+ rast->addLayer(p);
+#endif
+}
+
+typedef void (*raster_proc)(SkLayerRasterizer*, SkPaint&);
+
+#include "SkXfermode.h"
+
+static void apply_shader(SkPaint* paint, float scale)
+{
+ SkPaint p;
+ SkLayerRasterizer* rast = new SkLayerRasterizer;
+
+ p.setAntiAlias(true);
+ r7(rast, p, SkFloatToScalar(scale));
+ paint->setRasterizer(rast)->unref();
+
+ paint->setColor(SK_ColorBLUE);
+}
+
+class ClockFaceView : public SkView {
+ SkTypeface* fFace;
+ SkScalar fInterp;
+ SkScalar fDx;
+public:
+ ClockFaceView()
+ {
+ fFace = SkTypeface::CreateFromFile("/Users/reed/Downloads/p052024l.pfb");
+ fInterp = 0;
+ fDx = SK_Scalar1/64;
+ }
+
+ virtual ~ClockFaceView()
+ {
+ SkSafeUnref(fFace);
+ }
+
+protected:
+ // overrides from SkEventSink
+ virtual bool onQuery(SkEvent* evt)
+ {
+ if (SampleCode::TitleQ(*evt))
+ {
+ SampleCode::TitleR(evt, "Text Effects");
+ return true;
+ }
+ return this->INHERITED::onQuery(evt);
+ }
+
+ void drawBG(SkCanvas* canvas)
+ {
+// canvas->drawColor(0xFFDDDDDD);
+ canvas->drawColor(SK_ColorWHITE);
+ }
+
+ static void drawdots(SkCanvas* canvas, const SkPaint& orig) {
+ SkTDArray<SkPoint> pts;
+ SkPathEffect* pe = makepe(0, &pts);
+
+ SkStrokeRec rec(SkStrokeRec::kFill_InitStyle);
+ SkPath path, dstPath;
+ orig.getTextPath("9", 1, 0, 0, &path);
+ pe->filterPath(&dstPath, path, &rec);
+
+ SkPaint p;
+ p.setAntiAlias(true);
+ p.setStrokeWidth(10);
+ p.setColor(SK_ColorRED);
+ canvas->drawPoints(SkCanvas::kPoints_PointMode, pts.count(), pts.begin(),
+ p);
+ }
+
+ virtual void onDraw(SkCanvas* canvas) {
+ this->drawBG(canvas);
+
+ SkScalar x = SkIntToScalar(20);
+ SkScalar y = SkIntToScalar(300);
+ SkPaint paint;
+
+ paint.setAntiAlias(true);
+ paint.setTextSize(SkIntToScalar(240));
+ paint.setTypeface(SkTypeface::CreateFromName("sans-serif",
+ SkTypeface::kBold));
+
+ SkString str("9");
+
+ paint.setTypeface(fFace);
+
+ apply_shader(&paint, SkScalarToFloat(fInterp));
+ canvas->drawText(str.c_str(), str.size(), x, y, paint);
+
+ // drawdots(canvas, paint);
+
+ if (false) {
+ fInterp += fDx;
+ if (fInterp > 1) {
+ fInterp = 1;
+ fDx = -fDx;
+ } else if (fInterp < 0) {
+ fInterp = 0;
+ fDx = -fDx;
+ }
+ this->inval(NULL);
+ }
+ }
+
+private:
+ typedef SkView INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static SkView* MyFactory() { return new ClockFaceView; }
+static SkViewRegister reg(MyFactory);
+
diff --git a/samplecode/GMSampleView.h b/samplecode/GMSampleView.h
new file mode 100644
index 0000000000..6791c344c7
--- /dev/null
+++ b/samplecode/GMSampleView.h
@@ -0,0 +1,78 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#ifndef GMSampleView_DEFINED
+#define GMSampleView_DEFINED
+
+#include "SampleCode.h"
+#include "gm.h"
+
+class GMSampleView : public SampleView {
+private:
+ bool fShowSize;
+ typedef skiagm::GM GM;
+
+public:
+ GMSampleView(GM* gm)
+ : fShowSize(false), fGM(gm) {}
+
+ virtual ~GMSampleView() {
+ delete fGM;
+ }
+
+ static SkEvent* NewShowSizeEvt(bool doShowSize) {
+ SkEvent* evt = SkNEW_ARGS(SkEvent, ("GMSampleView::showSize"));
+ evt->setFast32(doShowSize);
+ return evt;
+ }
+
+protected:
+ virtual bool onQuery(SkEvent* evt) {
+ if (SampleCode::TitleQ(*evt)) {
+ SkString name("GM:");
+ name.append(fGM->shortName());
+ SampleCode::TitleR(evt, name.c_str());
+ return true;
+ }
+ return this->INHERITED::onQuery(evt);
+ }
+
+ virtual bool onEvent(const SkEvent& evt) SK_OVERRIDE {
+ if (evt.isType("GMSampleView::showSize")) {
+ fShowSize = SkToBool(evt.getFast32());
+ return true;
+ }
+ return this->INHERITED::onEvent(evt);
+ }
+
+ virtual void onDrawContent(SkCanvas* canvas) {
+ {
+ SkAutoCanvasRestore acr(canvas, fShowSize);
+ fGM->drawContent(canvas);
+ }
+ if (fShowSize) {
+ SkISize size = fGM->getISize();
+ SkRect r = SkRect::MakeWH(SkIntToScalar(size.width()),
+ SkIntToScalar(size.height()));
+ SkPaint paint;
+ paint.setColor(0x40FF8833);
+ canvas->drawRect(r, paint);
+ }
+ }
+
+ virtual void onDrawBackground(SkCanvas* canvas) {
+ fGM->drawBackground(canvas);
+ }
+
+private:
+ GM* fGM;
+ typedef SampleView INHERITED;
+};
+
+#endif
diff --git a/samplecode/SampleApp.cpp b/samplecode/SampleApp.cpp
new file mode 100644
index 0000000000..716998f960
--- /dev/null
+++ b/samplecode/SampleApp.cpp
@@ -0,0 +1,2504 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SampleApp.h"
+
+#include "SkData.h"
+#include "SkCanvas.h"
+#include "SkDevice.h"
+#include "SkGraphics.h"
+#include "SkImageEncoder.h"
+#include "SkPaint.h"
+#include "SkPicture.h"
+#include "SkStream.h"
+#include "SkTime.h"
+#include "SkWindow.h"
+
+#include "SampleCode.h"
+#include "SkTypeface.h"
+
+#if SK_SUPPORT_GPU
+#include "gl/GrGLInterface.h"
+#include "gl/GrGLUtil.h"
+#include "GrRenderTarget.h"
+#include "GrContext.h"
+#include "SkGpuDevice.h"
+#else
+class GrContext;
+#endif
+
+#include "SkOSFile.h"
+#include "SkPDFDevice.h"
+#include "SkPDFDocument.h"
+#include "SkStream.h"
+
+#include "SkGPipe.h"
+#include "SamplePipeControllers.h"
+#include "OverView.h"
+#include "TransitionView.h"
+
+SK_DEFINE_INST_COUNT(SampleWindow::DeviceManager)
+
+extern SampleView* CreateSamplePictFileView(const char filename[]);
+
+class PictFileFactory : public SkViewFactory {
+ SkString fFilename;
+public:
+ PictFileFactory(const SkString& filename) : fFilename(filename) {}
+ virtual SkView* operator() () const SK_OVERRIDE {
+ return CreateSamplePictFileView(fFilename.c_str());
+ }
+};
+
+#define PIPE_FILEx
+#ifdef PIPE_FILE
+#define FILE_PATH "/path/to/drawing.data"
+#endif
+
+#define PIPE_NETx
+#ifdef PIPE_NET
+#include "SkSockets.h"
+SkTCPServer gServer;
+#endif
+
+#define DEBUGGERx
+#ifdef DEBUGGER
+extern SkView* create_debugger(const char* data, size_t size);
+extern bool is_debugger(SkView* view);
+SkTDArray<char> gTempDataStore;
+#endif
+
+
+#define USE_ARROWS_FOR_ZOOM true
+
+#if SK_ANGLE
+//#define DEFAULT_TO_ANGLE 1
+#else
+//#define DEFAULT_TO_GPU 1
+#endif
+
+#define ANIMATING_EVENTTYPE "nextSample"
+#define ANIMATING_DELAY 750
+
+#ifdef SK_DEBUG
+ #define FPS_REPEAT_MULTIPLIER 1
+#else
+ #define FPS_REPEAT_MULTIPLIER 10
+#endif
+#define FPS_REPEAT_COUNT (10 * FPS_REPEAT_MULTIPLIER)
+
+static SampleWindow* gSampleWindow;
+
+static bool gShowGMBounds;
+
+static void postEventToSink(SkEvent* evt, SkEventSink* sink) {
+ evt->setTargetID(sink->getSinkID())->post();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static const char* skip_until(const char* str, const char* skip) {
+ if (!str) {
+ return NULL;
+ }
+ return strstr(str, skip);
+}
+
+static const char* skip_past(const char* str, const char* skip) {
+ const char* found = skip_until(str, skip);
+ if (!found) {
+ return NULL;
+ }
+ return found + strlen(skip);
+}
+
+static const char* gPrefFileName = "sampleapp_prefs.txt";
+
+static bool readTitleFromPrefs(SkString* title) {
+ SkFILEStream stream(gPrefFileName);
+ if (!stream.isValid()) {
+ return false;
+ }
+
+ int len = stream.getLength();
+ SkString data(len);
+ stream.read(data.writable_str(), len);
+ const char* s = data.c_str();
+
+ s = skip_past(s, "curr-slide-title");
+ s = skip_past(s, "=");
+ s = skip_past(s, "\"");
+ const char* stop = skip_until(s, "\"");
+ if (stop > s) {
+ title->set(s, stop - s);
+ return true;
+ }
+ return false;
+}
+
+static void writeTitleToPrefs(const char* title) {
+ SkFILEWStream stream(gPrefFileName);
+ SkString data;
+ data.printf("curr-slide-title = \"%s\"\n", title);
+ stream.write(data.c_str(), data.size());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SampleWindow::DefaultDeviceManager : public SampleWindow::DeviceManager {
+public:
+
+ DefaultDeviceManager() {
+#if SK_SUPPORT_GPU
+ fCurContext = NULL;
+ fCurIntf = NULL;
+ fCurRenderTarget = NULL;
+ fMSAASampleCount = 0;
+#endif
+ fBackend = kNone_BackEndType;
+ }
+
+ virtual ~DefaultDeviceManager() {
+#if SK_SUPPORT_GPU
+ SkSafeUnref(fCurContext);
+ SkSafeUnref(fCurIntf);
+ SkSafeUnref(fCurRenderTarget);
+#endif
+ }
+
+ virtual void setUpBackend(SampleWindow* win, int msaaSampleCount) {
+ SkASSERT(kNone_BackEndType == fBackend);
+
+ fBackend = kNone_BackEndType;
+
+#if SK_SUPPORT_GPU
+ switch (win->getDeviceType()) {
+ case kRaster_DeviceType:
+ // fallthrough
+ case kPicture_DeviceType:
+ // fallthrough
+ case kGPU_DeviceType:
+ // fallthrough
+ case kNullGPU_DeviceType:
+ // all these guys use the native backend
+ fBackend = kNativeGL_BackEndType;
+ break;
+#if SK_ANGLE
+ case kANGLE_DeviceType:
+ // ANGLE is really the only odd man out
+ fBackend = kANGLE_BackEndType;
+ break;
+#endif // SK_ANGLE
+ default:
+ SkASSERT(false);
+ break;
+ }
+
+ bool result = win->attach(fBackend, msaaSampleCount);
+ if (!result) {
+ SkDebugf("Failed to initialize GL");
+ return;
+ }
+ fMSAASampleCount = msaaSampleCount;
+
+ SkASSERT(NULL == fCurIntf);
+ switch (win->getDeviceType()) {
+ case kRaster_DeviceType:
+ // fallthrough
+ case kPicture_DeviceType:
+ // fallthrough
+ case kGPU_DeviceType:
+ // all these guys use the native interface
+ fCurIntf = GrGLCreateNativeInterface();
+ break;
+#if SK_ANGLE
+ case kANGLE_DeviceType:
+ fCurIntf = GrGLCreateANGLEInterface();
+ break;
+#endif // SK_ANGLE
+ case kNullGPU_DeviceType:
+ fCurIntf = GrGLCreateNullInterface();
+ break;
+ default:
+ SkASSERT(false);
+ break;
+ }
+
+ SkASSERT(NULL == fCurContext);
+ fCurContext = GrContext::Create(kOpenGL_GrBackend, (GrBackendContext) fCurIntf);
+
+ if (NULL == fCurContext || NULL == fCurIntf) {
+ // We need some context and interface to see results
+ SkSafeUnref(fCurContext);
+ SkSafeUnref(fCurIntf);
+ SkDebugf("Failed to setup 3D");
+
+ win->detach();
+ }
+#endif // SK_SUPPORT_GPU
+ // call windowSizeChanged to create the render target
+ this->windowSizeChanged(win);
+ }
+
+ virtual void tearDownBackend(SampleWindow *win) {
+#if SK_SUPPORT_GPU
+ SkSafeUnref(fCurContext);
+ fCurContext = NULL;
+
+ SkSafeUnref(fCurIntf);
+ fCurIntf = NULL;
+
+ SkSafeUnref(fCurRenderTarget);
+ fCurRenderTarget = NULL;
+#endif
+ win->detach();
+ fBackend = kNone_BackEndType;
+ }
+
+ virtual SkCanvas* createCanvas(SampleWindow::DeviceType dType,
+ SampleWindow* win) {
+ switch (dType) {
+ case kRaster_DeviceType:
+ // fallthrough
+ case kPicture_DeviceType:
+ // fallthrough
+#if SK_ANGLE
+ case kANGLE_DeviceType:
+#endif
+ break;
+#if SK_SUPPORT_GPU
+ case kGPU_DeviceType:
+ case kNullGPU_DeviceType:
+ if (fCurContext) {
+ SkAutoTUnref<SkDevice> device(new SkGpuDevice(fCurContext,
+ fCurRenderTarget));
+ return new SkCanvas(device);
+ } else {
+ return NULL;
+ }
+ break;
+#endif
+ default:
+ SkASSERT(false);
+ return NULL;
+ }
+ return NULL;
+ }
+
+ virtual void publishCanvas(SampleWindow::DeviceType dType,
+ SkCanvas* canvas,
+ SampleWindow* win) {
+#if SK_SUPPORT_GPU
+ if (fCurContext) {
+ // in case we have queued drawing calls
+ fCurContext->flush();
+
+ if (kGPU_DeviceType != dType && kNullGPU_DeviceType != dType) {
+ // need to send the raster bits to the (gpu) window
+ fCurContext->setRenderTarget(fCurRenderTarget);
+ const SkBitmap& bm = win->getBitmap();
+ fCurRenderTarget->writePixels(0, 0, bm.width(), bm.height(),
+ kSkia8888_PM_GrPixelConfig,
+ bm.getPixels(),
+ bm.rowBytes());
+ }
+ }
+#endif
+
+ win->present();
+ }
+
+ virtual void windowSizeChanged(SampleWindow* win) {
+#if SK_SUPPORT_GPU
+ if (fCurContext) {
+ win->attach(fBackend, fMSAASampleCount);
+
+ GrBackendRenderTargetDesc desc;
+ desc.fWidth = SkScalarRound(win->width());
+ desc.fHeight = SkScalarRound(win->height());
+ desc.fConfig = kSkia8888_PM_GrPixelConfig;
+ GR_GL_GetIntegerv(fCurIntf, GR_GL_SAMPLES, &desc.fSampleCnt);
+ GR_GL_GetIntegerv(fCurIntf, GR_GL_STENCIL_BITS, &desc.fStencilBits);
+ GrGLint buffer;
+ GR_GL_GetIntegerv(fCurIntf, GR_GL_FRAMEBUFFER_BINDING, &buffer);
+ desc.fRenderTargetHandle = buffer;
+
+ SkSafeUnref(fCurRenderTarget);
+ fCurRenderTarget = fCurContext->wrapBackendRenderTarget(desc);
+ }
+#endif
+ }
+
+ virtual GrContext* getGrContext() {
+#if SK_SUPPORT_GPU
+ return fCurContext;
+#else
+ return NULL;
+#endif
+ }
+
+ virtual GrRenderTarget* getGrRenderTarget() SK_OVERRIDE {
+#if SK_SUPPORT_GPU
+ return fCurRenderTarget;
+#else
+ return NULL;
+#endif
+ }
+
+private:
+
+#if SK_SUPPORT_GPU
+ GrContext* fCurContext;
+ const GrGLInterface* fCurIntf;
+ GrRenderTarget* fCurRenderTarget;
+ int fMSAASampleCount;
+#endif
+
+ SkOSWindow::SkBackEndTypes fBackend;
+
+ typedef SampleWindow::DeviceManager INHERITED;
+};
+
+///////////////
+static const char view_inval_msg[] = "view-inval-msg";
+
+void SampleWindow::postInvalDelay() {
+ (new SkEvent(view_inval_msg, this->getSinkID()))->postDelay(1);
+}
+
+static bool isInvalEvent(const SkEvent& evt) {
+ return evt.isType(view_inval_msg);
+}
+//////////////////
+
+SkFuncViewFactory::SkFuncViewFactory(SkViewCreateFunc func)
+ : fCreateFunc(func) {
+}
+
+SkView* SkFuncViewFactory::operator() () const {
+ return (*fCreateFunc)();
+}
+
+#include "GMSampleView.h"
+
+SkGMSampleViewFactory::SkGMSampleViewFactory(GMFactoryFunc func)
+ : fFunc(func) {
+}
+
+SkView* SkGMSampleViewFactory::operator() () const {
+ return new GMSampleView(fFunc(NULL));
+}
+
+SkViewRegister* SkViewRegister::gHead;
+SkViewRegister::SkViewRegister(SkViewFactory* fact) : fFact(fact) {
+ fFact->ref();
+ fChain = gHead;
+ gHead = this;
+}
+
+SkViewRegister::SkViewRegister(SkViewCreateFunc func) {
+ fFact = new SkFuncViewFactory(func);
+ fChain = gHead;
+ gHead = this;
+}
+
+SkViewRegister::SkViewRegister(GMFactoryFunc func) {
+ fFact = new SkGMSampleViewFactory(func);
+ fChain = gHead;
+ gHead = this;
+}
+
+class AutoUnrefArray {
+public:
+ AutoUnrefArray() {}
+ ~AutoUnrefArray() {
+ int count = fObjs.count();
+ for (int i = 0; i < count; ++i) {
+ fObjs[i]->unref();
+ }
+ }
+ SkRefCnt*& push_back() { return *fObjs.append(); }
+
+private:
+ SkTDArray<SkRefCnt*> fObjs;
+};
+
+// registers GMs as Samples
+// This can't be performed during static initialization because it could be
+// run before GMRegistry has been fully built.
+static void SkGMRegistyToSampleRegistry() {
+ static bool gOnce;
+ static AutoUnrefArray fRegisters;
+
+ if (!gOnce) {
+ const skiagm::GMRegistry* gmreg = skiagm::GMRegistry::Head();
+ while (gmreg) {
+ fRegisters.push_back() = new SkViewRegister(gmreg->factory());
+ gmreg = gmreg->next();
+ }
+ gOnce = true;
+ }
+}
+
+#if 0
+#include <CoreFoundation/CoreFoundation.h>
+#include <CoreFoundation/CFURLAccess.h>
+
+static void testpdf() {
+ CFStringRef path = CFStringCreateWithCString(NULL, "/test.pdf",
+ kCFStringEncodingUTF8);
+ CFURLRef url = CFURLCreateWithFileSystemPath(NULL, path,
+ kCFURLPOSIXPathStyle,
+ false);
+ CFRelease(path);
+ CGRect box = CGRectMake(0, 0, 8*72, 10*72);
+ CGContextRef cg = CGPDFContextCreateWithURL(url, &box, NULL);
+ CFRelease(url);
+
+ CGContextBeginPage(cg, &box);
+ CGRect r = CGRectMake(10, 10, 40 + 0.5, 50 + 0.5);
+ CGContextFillEllipseInRect(cg, r);
+ CGContextEndPage(cg);
+ CGContextRelease(cg);
+
+ if (false) {
+ SkBitmap bm;
+ bm.setConfig(SkBitmap::kA8_Config, 64, 64);
+ bm.allocPixels();
+ bm.eraseColor(SK_ColorTRANSPARENT);
+
+ SkCanvas canvas(bm);
+
+ }
+}
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+
+enum FlipAxisEnum {
+ kFlipAxis_X = (1 << 0),
+ kFlipAxis_Y = (1 << 1)
+};
+
+#include "SkDrawFilter.h"
+
+class FlagsDrawFilter : public SkDrawFilter {
+public:
+ FlagsDrawFilter(SkOSMenu::TriState lcd, SkOSMenu::TriState aa, SkOSMenu::TriState filter,
+ SkOSMenu::TriState hinting) :
+ fLCDState(lcd), fAAState(aa), fFilterState(filter), fHintingState(hinting) {}
+
+ virtual bool filter(SkPaint* paint, Type t) {
+ if (kText_Type == t && SkOSMenu::kMixedState != fLCDState) {
+ paint->setLCDRenderText(SkOSMenu::kOnState == fLCDState);
+ }
+ if (SkOSMenu::kMixedState != fAAState) {
+ paint->setAntiAlias(SkOSMenu::kOnState == fAAState);
+ }
+ if (SkOSMenu::kMixedState != fFilterState) {
+ paint->setFilterBitmap(SkOSMenu::kOnState == fFilterState);
+ }
+ if (SkOSMenu::kMixedState != fHintingState) {
+ paint->setHinting(SkOSMenu::kOnState == fHintingState ?
+ SkPaint::kNormal_Hinting :
+ SkPaint::kSlight_Hinting);
+ }
+ return true;
+ }
+
+private:
+ SkOSMenu::TriState fLCDState;
+ SkOSMenu::TriState fAAState;
+ SkOSMenu::TriState fFilterState;
+ SkOSMenu::TriState fHintingState;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+#define MAX_ZOOM_LEVEL 8
+#define MIN_ZOOM_LEVEL -8
+
+static const char gCharEvtName[] = "SampleCode_Char_Event";
+static const char gKeyEvtName[] = "SampleCode_Key_Event";
+static const char gTitleEvtName[] = "SampleCode_Title_Event";
+static const char gPrefSizeEvtName[] = "SampleCode_PrefSize_Event";
+static const char gFastTextEvtName[] = "SampleCode_FastText_Event";
+static const char gUpdateWindowTitleEvtName[] = "SampleCode_UpdateWindowTitle";
+
+bool SampleCode::CharQ(const SkEvent& evt, SkUnichar* outUni) {
+ if (evt.isType(gCharEvtName, sizeof(gCharEvtName) - 1)) {
+ if (outUni) {
+ *outUni = evt.getFast32();
+ }
+ return true;
+ }
+ return false;
+}
+
+bool SampleCode::KeyQ(const SkEvent& evt, SkKey* outKey) {
+ if (evt.isType(gKeyEvtName, sizeof(gKeyEvtName) - 1)) {
+ if (outKey) {
+ *outKey = (SkKey)evt.getFast32();
+ }
+ return true;
+ }
+ return false;
+}
+
+bool SampleCode::TitleQ(const SkEvent& evt) {
+ return evt.isType(gTitleEvtName, sizeof(gTitleEvtName) - 1);
+}
+
+void SampleCode::TitleR(SkEvent* evt, const char title[]) {
+ SkASSERT(evt && TitleQ(*evt));
+ evt->setString(gTitleEvtName, title);
+}
+
+bool SampleCode::RequestTitle(SkView* view, SkString* title) {
+ SkEvent evt(gTitleEvtName);
+ if (view->doQuery(&evt)) {
+ title->set(evt.findString(gTitleEvtName));
+ return true;
+ }
+ return false;
+}
+
+bool SampleCode::PrefSizeQ(const SkEvent& evt) {
+ return evt.isType(gPrefSizeEvtName, sizeof(gPrefSizeEvtName) - 1);
+}
+
+void SampleCode::PrefSizeR(SkEvent* evt, SkScalar width, SkScalar height) {
+ SkASSERT(evt && PrefSizeQ(*evt));
+ SkScalar size[2];
+ size[0] = width;
+ size[1] = height;
+ evt->setScalars(gPrefSizeEvtName, 2, size);
+}
+
+bool SampleCode::FastTextQ(const SkEvent& evt) {
+ return evt.isType(gFastTextEvtName, sizeof(gFastTextEvtName) - 1);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static SkMSec gAnimTime;
+static SkMSec gAnimTimePrev;
+
+SkMSec SampleCode::GetAnimTime() { return gAnimTime; }
+SkMSec SampleCode::GetAnimTimeDelta() { return gAnimTime - gAnimTimePrev; }
+SkScalar SampleCode::GetAnimSecondsDelta() {
+ return SkDoubleToScalar(GetAnimTimeDelta() / 1000.0);
+}
+
+SkScalar SampleCode::GetAnimScalar(SkScalar speed, SkScalar period) {
+ // since gAnimTime can be up to 32 bits, we can't convert it to a float
+ // or we'll lose the low bits. Hence we use doubles for the intermediate
+ // calculations
+ double seconds = (double)gAnimTime / 1000.0;
+ double value = SkScalarToDouble(speed) * seconds;
+ if (period) {
+ value = ::fmod(value, SkScalarToDouble(period));
+ }
+ return SkDoubleToScalar(value);
+}
+
+SkScalar SampleCode::GetAnimSinScalar(SkScalar amplitude,
+ SkScalar periodInSec,
+ SkScalar phaseInSec) {
+ if (!periodInSec) {
+ return 0;
+ }
+ double t = (double)gAnimTime / 1000.0 + phaseInSec;
+ t *= SkScalarToFloat(2 * SK_ScalarPI) / periodInSec;
+ amplitude = SK_ScalarHalf * amplitude;
+ return SkScalarMul(amplitude, SkDoubleToScalar(sin(t))) + amplitude;
+}
+
+GrContext* SampleCode::GetGr() {
+ return gSampleWindow ? gSampleWindow->getGrContext() : NULL;
+}
+
+// some GMs rely on having a skiagm::GetGr function defined
+namespace skiagm {
+ // FIXME: this should be moved into a header
+ GrContext* GetGr();
+ GrContext* GetGr() { return SampleCode::GetGr(); }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+static SkView* curr_view(SkWindow* wind) {
+ SkView::F2BIter iter(wind);
+ return iter.next();
+}
+
+static bool curr_title(SkWindow* wind, SkString* title) {
+ SkView* view = curr_view(wind);
+ if (view) {
+ SkEvent evt(gTitleEvtName);
+ if (view->doQuery(&evt)) {
+ title->set(evt.findString(gTitleEvtName));
+ return true;
+ }
+ }
+ return false;
+}
+
+void SampleWindow::setZoomCenter(float x, float y)
+{
+ fZoomCenterX = SkFloatToScalar(x);
+ fZoomCenterY = SkFloatToScalar(y);
+}
+
+bool SampleWindow::zoomIn()
+{
+ // Arbitrarily decided
+ if (fFatBitsScale == 25) return false;
+ fFatBitsScale++;
+ this->inval(NULL);
+ return true;
+}
+
+bool SampleWindow::zoomOut()
+{
+ if (fFatBitsScale == 1) return false;
+ fFatBitsScale--;
+ this->inval(NULL);
+ return true;
+}
+
+void SampleWindow::updatePointer(int x, int y)
+{
+ fMouseX = x;
+ fMouseY = y;
+ if (fShowZoomer) {
+ this->inval(NULL);
+ }
+}
+
+static inline SampleWindow::DeviceType cycle_devicetype(SampleWindow::DeviceType ct) {
+ static const SampleWindow::DeviceType gCT[] = {
+ SampleWindow::kPicture_DeviceType,
+#if SK_SUPPORT_GPU
+ SampleWindow::kGPU_DeviceType,
+#if SK_ANGLE
+ SampleWindow::kANGLE_DeviceType,
+#endif // SK_ANGLE
+ SampleWindow::kRaster_DeviceType, // skip the null gpu device in normal cycling
+#endif // SK_SUPPORT_GPU
+ SampleWindow::kRaster_DeviceType
+ };
+ SK_COMPILE_ASSERT(SK_ARRAY_COUNT(gCT) == SampleWindow::kDeviceTypeCnt, array_size_mismatch);
+ return gCT[ct];
+}
+
+static void usage(const char * argv0) {
+ SkDebugf("%s [--slide sampleName] [-i resourcePath] [--msaa sampleCount] [--pictureDir dirPath] [--picture path]\n", argv0);
+ SkDebugf(" sampleName: sample at which to start.\n");
+ SkDebugf(" resourcePath: directory that stores image resources.\n");
+ SkDebugf(" msaa: request multisampling with the given sample count.\n");
+ SkDebugf(" dirPath: path to directory skia pictures are read from\n");
+ SkDebugf(" path: path to skia picture\n");
+}
+
+SampleWindow::SampleWindow(void* hwnd, int argc, char** argv, DeviceManager* devManager)
+ : INHERITED(hwnd)
+ , fDevManager(NULL) {
+
+ this->registerPictFileSamples(argv, argc);
+ this->registerPictFileSample(argv, argc);
+ SkGMRegistyToSampleRegistry();
+ {
+ const SkViewRegister* reg = SkViewRegister::Head();
+ while (reg) {
+ *fSamples.append() = reg->factory();
+ reg = reg->next();
+ }
+ }
+
+ const char* resourcePath = NULL;
+ fCurrIndex = -1;
+ fMSAASampleCount = 0;
+
+ const char* const commandName = argv[0];
+ char* const* stop = argv + argc;
+ for (++argv; argv < stop; ++argv) {
+ if (strcmp(*argv, "-i") == 0) {
+ argv++;
+ if (argv < stop && **argv) {
+ resourcePath = *argv;
+ }
+ } else if (strcmp(*argv, "--slide") == 0) {
+ argv++;
+ if (argv < stop && **argv) {
+ fCurrIndex = findByTitle(*argv);
+ if (fCurrIndex < 0) {
+ fprintf(stderr, "Unknown sample \"%s\"\n", *argv);
+ listTitles();
+ }
+ }
+ } else if (strcmp(*argv, "--msaa") == 0) {
+ ++argv;
+ if (argv < stop && **argv) {
+ fMSAASampleCount = atoi(*argv);
+ }
+ } else if (strcmp(*argv, "--list") == 0) {
+ listTitles();
+ }
+ else {
+ usage(commandName);
+ }
+ }
+
+ if (fCurrIndex < 0) {
+ SkString title;
+ if (readTitleFromPrefs(&title)) {
+ fCurrIndex = findByTitle(title.c_str());
+ }
+ }
+
+ if (fCurrIndex < 0) {
+ fCurrIndex = 0;
+ }
+
+ gSampleWindow = this;
+
+#ifdef PIPE_FILE
+ //Clear existing file or create file if it doesn't exist
+ FILE* f = fopen(FILE_PATH, "wb");
+ fclose(f);
+#endif
+
+ fPicture = NULL;
+
+ fDeviceType = kRaster_DeviceType;
+
+#if DEFAULT_TO_GPU
+ fDeviceType = kGPU_DeviceType;
+#endif
+#if SK_ANGLE && DEFAULT_TO_ANGLE
+ fDeviceType = kANGLE_DeviceType;
+#endif
+
+ fUseClip = false;
+ fNClip = false;
+ fAnimating = false;
+ fRotate = false;
+ fPerspAnim = false;
+ fPerspAnimTime = 0;
+ fScale = false;
+ fRequestGrabImage = false;
+ fPipeState = SkOSMenu::kOffState;
+ fTilingState = SkOSMenu::kOffState;
+ fTileCount.set(1, 1);
+ fMeasureFPS = false;
+ fLCDState = SkOSMenu::kMixedState;
+ fAAState = SkOSMenu::kMixedState;
+ fFilterState = SkOSMenu::kMixedState;
+ fHintingState = SkOSMenu::kMixedState;
+ fFlipAxis = 0;
+ fScrollTestX = fScrollTestY = 0;
+
+ fMouseX = fMouseY = 0;
+ fFatBitsScale = 8;
+ fTypeface = SkTypeface::CreateFromTypeface(NULL, SkTypeface::kBold);
+ fShowZoomer = false;
+
+ fZoomLevel = 0;
+ fZoomScale = SK_Scalar1;
+
+ fMagnify = false;
+ fDebugger = false;
+
+ fSaveToPdf = false;
+ fPdfCanvas = NULL;
+
+ fTransitionNext = 6;
+ fTransitionPrev = 2;
+
+ int sinkID = this->getSinkID();
+ fAppMenu = new SkOSMenu;
+ fAppMenu->setTitle("Global Settings");
+ int itemID;
+
+ itemID =fAppMenu->appendList("Device Type", "Device Type", sinkID, 0,
+ "Raster", "Picture", "OpenGL",
+#if SK_ANGLE
+ "ANGLE",
+#endif
+ NULL);
+ fAppMenu->assignKeyEquivalentToItem(itemID, 'd');
+ itemID = fAppMenu->appendTriState("AA", "AA", sinkID, fAAState);
+ fAppMenu->assignKeyEquivalentToItem(itemID, 'b');
+ itemID = fAppMenu->appendTriState("LCD", "LCD", sinkID, fLCDState);
+ fAppMenu->assignKeyEquivalentToItem(itemID, 'l');
+ itemID = fAppMenu->appendTriState("Filter", "Filter", sinkID, fFilterState);
+ fAppMenu->assignKeyEquivalentToItem(itemID, 'n');
+ itemID = fAppMenu->appendTriState("Hinting", "Hinting", sinkID, fHintingState);
+ fAppMenu->assignKeyEquivalentToItem(itemID, 'h');
+
+ fUsePipeMenuItemID = fAppMenu->appendTriState("Pipe", "Pipe" , sinkID,
+ fPipeState);
+ fAppMenu->assignKeyEquivalentToItem(fUsePipeMenuItemID, 'P');
+
+ itemID = fAppMenu->appendTriState("Tiling", "Tiling", sinkID, fTilingState);
+ fAppMenu->assignKeyEquivalentToItem(itemID, 't');
+
+#ifdef DEBUGGER
+ itemID = fAppMenu->appendSwitch("Debugger", "Debugger", sinkID, fDebugger);
+ fAppMenu->assignKeyEquivalentToItem(itemID, 'q');
+#endif
+ itemID = fAppMenu->appendSwitch("Slide Show", "Slide Show" , sinkID, false);
+ fAppMenu->assignKeyEquivalentToItem(itemID, 'a');
+ itemID = fAppMenu->appendSwitch("Clip", "Clip" , sinkID, fUseClip);
+ fAppMenu->assignKeyEquivalentToItem(itemID, 'c');
+ itemID = fAppMenu->appendSwitch("Flip X", "Flip X" , sinkID, false);
+ fAppMenu->assignKeyEquivalentToItem(itemID, 'x');
+ itemID = fAppMenu->appendSwitch("Flip Y", "Flip Y" , sinkID, false);
+ fAppMenu->assignKeyEquivalentToItem(itemID, 'y');
+ itemID = fAppMenu->appendSwitch("Zoomer", "Zoomer" , sinkID, fShowZoomer);
+ fAppMenu->assignKeyEquivalentToItem(itemID, 'z');
+ itemID = fAppMenu->appendSwitch("Magnify", "Magnify" , sinkID, fMagnify);
+ fAppMenu->assignKeyEquivalentToItem(itemID, 'm');
+ itemID =fAppMenu->appendList("Transition-Next", "Transition-Next", sinkID,
+ fTransitionNext, "Up", "Up and Right", "Right",
+ "Down and Right", "Down", "Down and Left",
+ "Left", "Up and Left", NULL);
+ fAppMenu->assignKeyEquivalentToItem(itemID, 'j');
+ itemID =fAppMenu->appendList("Transition-Prev", "Transition-Prev", sinkID,
+ fTransitionPrev, "Up", "Up and Right", "Right",
+ "Down and Right", "Down", "Down and Left",
+ "Left", "Up and Left", NULL);
+ fAppMenu->assignKeyEquivalentToItem(itemID, 'k');
+ itemID = fAppMenu->appendAction("Save to PDF", sinkID);
+ fAppMenu->assignKeyEquivalentToItem(itemID, 'e');
+
+ this->addMenu(fAppMenu);
+ fSlideMenu = new SkOSMenu;
+ this->addMenu(fSlideMenu);
+
+// this->setConfig(SkBitmap::kRGB_565_Config);
+ this->setConfig(SkBitmap::kARGB_8888_Config);
+ this->setVisibleP(true);
+ this->setClipToBounds(false);
+
+ skiagm::GM::SetResourcePath(resourcePath);
+
+ this->loadView((*fSamples[fCurrIndex])());
+
+ fPDFData = NULL;
+
+ if (NULL == devManager) {
+ fDevManager = new DefaultDeviceManager();
+ } else {
+ devManager->ref();
+ fDevManager = devManager;
+ }
+ fDevManager->setUpBackend(this, fMSAASampleCount);
+
+ // If another constructor set our dimensions, ensure that our
+ // onSizeChange gets called.
+ if (this->height() && this->width()) {
+ this->onSizeChange();
+ }
+
+ // can't call this synchronously, since it may require a subclass to
+ // to implement, or the caller may need us to have returned from the
+ // constructor first. Hence we post an event to ourselves.
+// this->updateTitle();
+ postEventToSink(new SkEvent(gUpdateWindowTitleEvtName), this);
+}
+
+SampleWindow::~SampleWindow() {
+ delete fPicture;
+ delete fPdfCanvas;
+ fTypeface->unref();
+
+ SkSafeUnref(fDevManager);
+}
+
+static void make_filepath(SkString* path, const char* dir, const SkString& name) {
+ size_t len = strlen(dir);
+ path->set(dir);
+ if (len > 0 && dir[len - 1] != '/') {
+ path->append("/");
+ }
+ path->append(name);
+}
+
+void SampleWindow::registerPictFileSample(char** argv, int argc) {
+ const char* pict = NULL;
+
+ for (int i = 0; i < argc; ++i) {
+ if (!strcmp(argv[i], "--picture")) {
+ i += 1;
+ if (i < argc) {
+ pict = argv[i];
+ break;
+ }
+ }
+ }
+ if (pict) {
+ SkString path(pict);
+ *fSamples.append() = new PictFileFactory(path);
+ }
+}
+
+void SampleWindow::registerPictFileSamples(char** argv, int argc) {
+ const char* pictDir = NULL;
+
+ for (int i = 0; i < argc; ++i) {
+ if (!strcmp(argv[i], "--pictureDir")) {
+ i += 1;
+ if (i < argc) {
+ pictDir = argv[i];
+ break;
+ }
+ }
+ }
+ if (pictDir) {
+ SkOSFile::Iter iter(pictDir, "skp");
+ SkString filename;
+ while (iter.next(&filename)) {
+ SkString path;
+ make_filepath(&path, pictDir, filename);
+ *fSamples.append() = new PictFileFactory(path);
+ }
+ }
+}
+
+int SampleWindow::findByTitle(const char title[]) {
+ int i, count = fSamples.count();
+ for (i = 0; i < count; i++) {
+ if (getSampleTitle(i).equals(title)) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+void SampleWindow::listTitles() {
+ int count = fSamples.count();
+ SkDebugf("All Slides:\n");
+ for (int i = 0; i < count; i++) {
+ SkDebugf(" %s\n", getSampleTitle(i).c_str());
+ }
+}
+
+static SkBitmap capture_bitmap(SkCanvas* canvas) {
+ SkBitmap bm;
+ const SkBitmap& src = canvas->getDevice()->accessBitmap(false);
+ src.copyTo(&bm, src.config());
+ return bm;
+}
+
+static bool bitmap_diff(SkCanvas* canvas, const SkBitmap& orig,
+ SkBitmap* diff) {
+ const SkBitmap& src = canvas->getDevice()->accessBitmap(false);
+
+ SkAutoLockPixels alp0(src);
+ SkAutoLockPixels alp1(orig);
+ for (int y = 0; y < src.height(); y++) {
+ const void* srcP = src.getAddr(0, y);
+ const void* origP = orig.getAddr(0, y);
+ size_t bytes = src.width() * src.bytesPerPixel();
+ if (memcmp(srcP, origP, bytes)) {
+ SkDebugf("---------- difference on line %d\n", y);
+ return true;
+ }
+ }
+ return false;
+}
+
+static void drawText(SkCanvas* canvas, SkString string, SkScalar left, SkScalar top, SkPaint& paint)
+{
+ SkColor desiredColor = paint.getColor();
+ paint.setColor(SK_ColorWHITE);
+ const char* c_str = string.c_str();
+ size_t size = string.size();
+ SkRect bounds;
+ paint.measureText(c_str, size, &bounds);
+ bounds.offset(left, top);
+ SkScalar inset = SkIntToScalar(-2);
+ bounds.inset(inset, inset);
+ canvas->drawRect(bounds, paint);
+ if (desiredColor != SK_ColorBLACK) {
+ paint.setColor(SK_ColorBLACK);
+ canvas->drawText(c_str, size, left + SK_Scalar1, top + SK_Scalar1, paint);
+ }
+ paint.setColor(desiredColor);
+ canvas->drawText(c_str, size, left, top, paint);
+}
+
+#define XCLIP_N 8
+#define YCLIP_N 8
+
+void SampleWindow::draw(SkCanvas* canvas) {
+ // update the animation time
+ if (!gAnimTimePrev && !gAnimTime) {
+ // first time make delta be 0
+ gAnimTime = SkTime::GetMSecs();
+ gAnimTimePrev = gAnimTime;
+ } else {
+ gAnimTimePrev = gAnimTime;
+ gAnimTime = SkTime::GetMSecs();
+ }
+
+ if (fGesture.isActive()) {
+ this->updateMatrix();
+ }
+
+ if (fMeasureFPS) {
+ fMeasureFPS_Time = 0;
+ }
+
+ if (fNClip) {
+ this->INHERITED::draw(canvas);
+ SkBitmap orig = capture_bitmap(canvas);
+
+ const SkScalar w = this->width();
+ const SkScalar h = this->height();
+ const SkScalar cw = w / XCLIP_N;
+ const SkScalar ch = h / YCLIP_N;
+ for (int y = 0; y < YCLIP_N; y++) {
+ SkRect r;
+ r.fTop = y * ch;
+ r.fBottom = (y + 1) * ch;
+ if (y == YCLIP_N - 1) {
+ r.fBottom = h;
+ }
+ for (int x = 0; x < XCLIP_N; x++) {
+ SkAutoCanvasRestore acr(canvas, true);
+ r.fLeft = x * cw;
+ r.fRight = (x + 1) * cw;
+ if (x == XCLIP_N - 1) {
+ r.fRight = w;
+ }
+ canvas->clipRect(r);
+ this->INHERITED::draw(canvas);
+ }
+ }
+
+ SkBitmap diff;
+ if (bitmap_diff(canvas, orig, &diff)) {
+ }
+ } else {
+ const SkScalar cw = SkScalarDiv(this->width(), SkIntToScalar(fTileCount.width()));
+ const SkScalar ch = SkScalarDiv(this->height(), SkIntToScalar(fTileCount.height()));
+
+ for (int y = 0; y < fTileCount.height(); ++y) {
+ for (int x = 0; x < fTileCount.width(); ++x) {
+ SkAutoCanvasRestore acr(canvas, true);
+ canvas->clipRect(SkRect::MakeXYWH(x * cw, y * ch, cw, ch));
+ this->INHERITED::draw(canvas);
+ }
+ }
+
+ if (!fTileCount.equals(1, 1)) {
+ SkPaint paint;
+ paint.setColor(0x60FF00FF);
+ paint.setStyle(SkPaint::kStroke_Style);
+ for (int y = 0; y < fTileCount.height(); ++y) {
+ for (int x = 0; x < fTileCount.width(); ++x) {
+ canvas->drawRect(SkRect::MakeXYWH(x * cw, y * ch, cw, ch), paint);
+ }
+ }
+ }
+ }
+ if (fShowZoomer && !fSaveToPdf) {
+ showZoomer(canvas);
+ }
+ if (fMagnify && !fSaveToPdf) {
+ magnify(canvas);
+ }
+
+ if (fMeasureFPS && fMeasureFPS_Time) {
+ this->updateTitle();
+ this->postInvalDelay();
+ }
+
+ // do this last
+ fDevManager->publishCanvas(fDeviceType, canvas, this);
+}
+
+static float clipW = 200;
+static float clipH = 200;
+void SampleWindow::magnify(SkCanvas* canvas) {
+ SkRect r;
+ int count = canvas->save();
+
+ SkMatrix m = canvas->getTotalMatrix();
+ if (!m.invert(&m)) {
+ return;
+ }
+ SkPoint offset, center;
+ SkScalar mouseX = fMouseX * SK_Scalar1;
+ SkScalar mouseY = fMouseY * SK_Scalar1;
+ m.mapXY(mouseX - clipW/2, mouseY - clipH/2, &offset);
+ m.mapXY(mouseX, mouseY, &center);
+
+ r.set(0, 0, clipW * m.getScaleX(), clipH * m.getScaleX());
+ r.offset(offset.fX, offset.fY);
+
+ SkPaint paint;
+ paint.setColor(0xFF66AAEE);
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setStrokeWidth(10.f * m.getScaleX());
+ //lense offset
+ //canvas->translate(0, -250);
+ canvas->drawRect(r, paint);
+ canvas->clipRect(r);
+
+ m = canvas->getTotalMatrix();
+ m.setTranslate(-center.fX, -center.fY);
+ m.postScale(0.5f * fFatBitsScale, 0.5f * fFatBitsScale);
+ m.postTranslate(center.fX, center.fY);
+ canvas->concat(m);
+
+ this->INHERITED::draw(canvas);
+
+ canvas->restoreToCount(count);
+}
+
+void SampleWindow::showZoomer(SkCanvas* canvas) {
+ int count = canvas->save();
+ canvas->resetMatrix();
+ // Ensure the mouse position is on screen.
+ int width = SkScalarRound(this->width());
+ int height = SkScalarRound(this->height());
+ if (fMouseX >= width) fMouseX = width - 1;
+ else if (fMouseX < 0) fMouseX = 0;
+ if (fMouseY >= height) fMouseY = height - 1;
+ else if (fMouseY < 0) fMouseY = 0;
+
+ SkBitmap bitmap = capture_bitmap(canvas);
+ bitmap.lockPixels();
+
+ // Find the size of the zoomed in view, forced to be odd, so the examined pixel is in the middle.
+ int zoomedWidth = (width >> 1) | 1;
+ int zoomedHeight = (height >> 1) | 1;
+ SkIRect src;
+ src.set(0, 0, zoomedWidth / fFatBitsScale, zoomedHeight / fFatBitsScale);
+ src.offset(fMouseX - (src.width()>>1), fMouseY - (src.height()>>1));
+ SkRect dest;
+ dest.set(0, 0, SkIntToScalar(zoomedWidth), SkIntToScalar(zoomedHeight));
+ dest.offset(SkIntToScalar(width - zoomedWidth), SkIntToScalar(height - zoomedHeight));
+ SkPaint paint;
+ // Clear the background behind our zoomed in view
+ paint.setColor(SK_ColorWHITE);
+ canvas->drawRect(dest, paint);
+ canvas->drawBitmapRect(bitmap, &src, dest);
+ paint.setColor(SK_ColorBLACK);
+ paint.setStyle(SkPaint::kStroke_Style);
+ // Draw a border around the pixel in the middle
+ SkRect originalPixel;
+ originalPixel.set(SkIntToScalar(fMouseX), SkIntToScalar(fMouseY), SkIntToScalar(fMouseX + 1), SkIntToScalar(fMouseY + 1));
+ SkMatrix matrix;
+ SkRect scalarSrc;
+ scalarSrc.set(src);
+ SkColor color = bitmap.getColor(fMouseX, fMouseY);
+ if (matrix.setRectToRect(scalarSrc, dest, SkMatrix::kFill_ScaleToFit)) {
+ SkRect pixel;
+ matrix.mapRect(&pixel, originalPixel);
+ // TODO Perhaps measure the values and make the outline white if it's "dark"
+ if (color == SK_ColorBLACK) {
+ paint.setColor(SK_ColorWHITE);
+ }
+ canvas->drawRect(pixel, paint);
+ }
+ paint.setColor(SK_ColorBLACK);
+ // Draw a border around the destination rectangle
+ canvas->drawRect(dest, paint);
+ paint.setStyle(SkPaint::kStrokeAndFill_Style);
+ // Identify the pixel and its color on screen
+ paint.setTypeface(fTypeface);
+ paint.setAntiAlias(true);
+ SkScalar lineHeight = paint.getFontMetrics(NULL);
+ SkString string;
+ string.appendf("(%i, %i)", fMouseX, fMouseY);
+ SkScalar left = dest.fLeft + SkIntToScalar(3);
+ SkScalar i = SK_Scalar1;
+ drawText(canvas, string, left, SkScalarMulAdd(lineHeight, i, dest.fTop), paint);
+ // Alpha
+ i += SK_Scalar1;
+ string.reset();
+ string.appendf("A: %X", SkColorGetA(color));
+ drawText(canvas, string, left, SkScalarMulAdd(lineHeight, i, dest.fTop), paint);
+ // Red
+ i += SK_Scalar1;
+ string.reset();
+ string.appendf("R: %X", SkColorGetR(color));
+ paint.setColor(SK_ColorRED);
+ drawText(canvas, string, left, SkScalarMulAdd(lineHeight, i, dest.fTop), paint);
+ // Green
+ i += SK_Scalar1;
+ string.reset();
+ string.appendf("G: %X", SkColorGetG(color));
+ paint.setColor(SK_ColorGREEN);
+ drawText(canvas, string, left, SkScalarMulAdd(lineHeight, i, dest.fTop), paint);
+ // Blue
+ i += SK_Scalar1;
+ string.reset();
+ string.appendf("B: %X", SkColorGetB(color));
+ paint.setColor(SK_ColorBLUE);
+ drawText(canvas, string, left, SkScalarMulAdd(lineHeight, i, dest.fTop), paint);
+ canvas->restoreToCount(count);
+}
+
+void SampleWindow::onDraw(SkCanvas* canvas) {
+}
+
+#include "SkColorPriv.h"
+
+#if 0 // UNUSED
+static void reverseRedAndBlue(const SkBitmap& bm) {
+ SkASSERT(bm.config() == SkBitmap::kARGB_8888_Config);
+ uint8_t* p = (uint8_t*)bm.getPixels();
+ uint8_t* stop = p + bm.getSize();
+ while (p < stop) {
+ // swap red/blue (to go from ARGB(int) to RGBA(memory) and premultiply
+ unsigned scale = SkAlpha255To256(p[3]);
+ unsigned r = p[2];
+ unsigned b = p[0];
+ p[0] = SkAlphaMul(r, scale);
+ p[1] = SkAlphaMul(p[1], scale);
+ p[2] = SkAlphaMul(b, scale);
+ p += 4;
+ }
+}
+#endif
+
+void SampleWindow::saveToPdf()
+{
+ fSaveToPdf = true;
+ this->inval(NULL);
+}
+
+SkCanvas* SampleWindow::beforeChildren(SkCanvas* canvas) {
+ if (fSaveToPdf) {
+ const SkBitmap& bmp = canvas->getDevice()->accessBitmap(false);
+ SkISize size = SkISize::Make(bmp.width(), bmp.height());
+ SkPDFDevice* pdfDevice = new SkPDFDevice(size, size,
+ canvas->getTotalMatrix());
+ fPdfCanvas = new SkCanvas(pdfDevice);
+ pdfDevice->unref();
+ canvas = fPdfCanvas;
+ } else {
+ switch (fDeviceType) {
+ case kRaster_DeviceType:
+ // fallthrough
+#if SK_SUPPORT_GPU
+ case kGPU_DeviceType:
+ // fallthrough
+#if SK_ANGLE
+ case kANGLE_DeviceType:
+#endif // SK_ANGLE
+#endif // SK_SUPPORT_GPU
+ canvas = this->INHERITED::beforeChildren(canvas);
+ break;
+ case kPicture_DeviceType:
+ fPicture = new SkPicture;
+ canvas = fPicture->beginRecording(9999, 9999);
+ break;
+#if SK_SUPPORT_GPU
+ case kNullGPU_DeviceType:
+ break;
+#endif
+ default:
+ SkASSERT(false);
+ break;
+ }
+ }
+
+ if (fUseClip) {
+ canvas->drawColor(0xFFFF88FF);
+ canvas->clipPath(fClipPath, SkRegion::kIntersect_Op, true);
+ }
+
+ return canvas;
+}
+
+static void paint_rgn(const SkBitmap& bm, const SkIRect& r,
+ const SkRegion& rgn) {
+ SkCanvas canvas(bm);
+ SkRegion inval(rgn);
+
+ inval.translate(r.fLeft, r.fTop);
+ canvas.clipRegion(inval);
+ canvas.drawColor(0xFFFF8080);
+}
+#include "SkData.h"
+void SampleWindow::afterChildren(SkCanvas* orig) {
+ if (fSaveToPdf) {
+ fSaveToPdf = false;
+ if (fShowZoomer) {
+ showZoomer(fPdfCanvas);
+ }
+ SkString name;
+ name.printf("%s.pdf", this->getTitle());
+ SkPDFDocument doc;
+ SkPDFDevice* device = static_cast<SkPDFDevice*>(fPdfCanvas->getDevice());
+ doc.appendPage(device);
+#ifdef SK_BUILD_FOR_ANDROID
+ name.prepend("/sdcard/");
+#endif
+
+#ifdef SK_BUILD_FOR_IOS
+ SkDynamicMemoryWStream mstream;
+ doc.emitPDF(&mstream);
+ fPDFData = mstream.copyToData();
+#endif
+ SkFILEWStream stream(name.c_str());
+ if (stream.isValid()) {
+ doc.emitPDF(&stream);
+ const char* desc = "File saved from Skia SampleApp";
+ this->onPDFSaved(this->getTitle(), desc, name.c_str());
+ }
+
+ delete fPdfCanvas;
+ fPdfCanvas = NULL;
+
+ // We took over the draw calls in order to create the PDF, so we need
+ // to redraw.
+ this->inval(NULL);
+ return;
+ }
+
+ if (fRequestGrabImage) {
+ fRequestGrabImage = false;
+
+ SkDevice* device = orig->getDevice();
+ SkBitmap bmp;
+ if (device->accessBitmap(false).copyTo(&bmp, SkBitmap::kARGB_8888_Config)) {
+ static int gSampleGrabCounter;
+ SkString name;
+ name.printf("sample_grab_%d.png", gSampleGrabCounter++);
+ SkImageEncoder::EncodeFile(name.c_str(), bmp,
+ SkImageEncoder::kPNG_Type, 100);
+ }
+ }
+
+ if (kPicture_DeviceType == fDeviceType) {
+ if (true) {
+ SkPicture* pict = new SkPicture(*fPicture);
+ fPicture->unref();
+ this->installDrawFilter(orig);
+ orig->drawPicture(*pict);
+ pict->unref();
+ } else if (true) {
+ SkDynamicMemoryWStream ostream;
+ fPicture->serialize(&ostream);
+ fPicture->unref();
+
+ SkAutoDataUnref data(ostream.copyToData());
+ SkMemoryStream istream(data->data(), data->size());
+ SkPicture pict(&istream);
+ orig->drawPicture(pict);
+ } else {
+ fPicture->draw(orig);
+ fPicture->unref();
+ }
+ fPicture = NULL;
+ }
+
+ // Do this after presentGL and other finishing, rather than in afterChild
+ if (fMeasureFPS && fMeasureFPS_StartTime) {
+ fMeasureFPS_Time += SkTime::GetMSecs() - fMeasureFPS_StartTime;
+ }
+
+ // if ((fScrollTestX | fScrollTestY) != 0)
+ if (false) {
+ const SkBitmap& bm = orig->getDevice()->accessBitmap(true);
+ int dx = fScrollTestX * 7;
+ int dy = fScrollTestY * 7;
+ SkIRect r;
+ SkRegion inval;
+
+ r.set(50, 50, 50+100, 50+100);
+ bm.scrollRect(&r, dx, dy, &inval);
+ paint_rgn(bm, r, inval);
+ }
+#ifdef DEBUGGER
+ SkView* curr = curr_view(this);
+ if (fDebugger && !is_debugger(curr) && !is_transition(curr) && !is_overview(curr)) {
+ //Stop Pipe when fDebugger is active
+ if (fPipeState != SkOSMenu::kOffState) {
+ fPipeState = SkOSMenu::kOffState;
+ (void)SampleView::SetUsePipe(curr, fPipeState);
+ fAppMenu->getItemByID(fUsePipeMenuItemID)->setTriState(fPipeState);
+ this->onUpdateMenu(fAppMenu);
+ }
+
+ //Reset any transformations
+ fGesture.stop();
+ fGesture.reset();
+
+ this->loadView(create_debugger(gTempDataStore.begin(),
+ gTempDataStore.count()));
+ }
+#endif
+}
+
+void SampleWindow::beforeChild(SkView* child, SkCanvas* canvas) {
+ if (fScale) {
+ SkScalar scale = SK_Scalar1 * 7 / 10;
+ SkScalar cx = this->width() / 2;
+ SkScalar cy = this->height() / 2;
+ canvas->translate(cx, cy);
+ canvas->scale(scale, scale);
+ canvas->translate(-cx, -cy);
+ }
+ if (fRotate) {
+ SkScalar cx = this->width() / 2;
+ SkScalar cy = this->height() / 2;
+ canvas->translate(cx, cy);
+ canvas->rotate(SkIntToScalar(30));
+ canvas->translate(-cx, -cy);
+ }
+ if (fPerspAnim) {
+ fPerspAnimTime += SampleCode::GetAnimSecondsDelta();
+
+ static const SkScalar gAnimPeriod = 10 * SK_Scalar1;
+ static const SkScalar gAnimMag = SK_Scalar1 / 1000;
+ SkScalar t = SkScalarMod(fPerspAnimTime, gAnimPeriod);
+ if (SkScalarFloorToInt(SkScalarDiv(fPerspAnimTime, gAnimPeriod)) & 0x1) {
+ t = gAnimPeriod - t;
+ }
+ t = 2 * t - gAnimPeriod;
+ t = SkScalarMul(SkScalarDiv(t, gAnimPeriod), gAnimMag);
+ SkMatrix m;
+ m.reset();
+ m.setPerspY(t);
+ canvas->concat(m);
+ }
+
+ this->installDrawFilter(canvas);
+
+ if (fMeasureFPS) {
+ if (SampleView::SetRepeatDraw(child, FPS_REPEAT_COUNT)) {
+ fMeasureFPS_StartTime = SkTime::GetMSecs();
+ }
+ } else {
+ (void)SampleView::SetRepeatDraw(child, 1);
+ }
+ if (fPerspAnim) {
+ this->inval(NULL);
+ }
+}
+
+void SampleWindow::afterChild(SkView* child, SkCanvas* canvas) {
+ canvas->setDrawFilter(NULL);
+}
+
+static SkBitmap::Config gConfigCycle[] = {
+ SkBitmap::kNo_Config, // none -> none
+ SkBitmap::kNo_Config, // a1 -> none
+ SkBitmap::kNo_Config, // a8 -> none
+ SkBitmap::kNo_Config, // index8 -> none
+ SkBitmap::kARGB_4444_Config, // 565 -> 4444
+ SkBitmap::kARGB_8888_Config, // 4444 -> 8888
+ SkBitmap::kRGB_565_Config // 8888 -> 565
+};
+
+static SkBitmap::Config cycle_configs(SkBitmap::Config c) {
+ return gConfigCycle[c];
+}
+
+void SampleWindow::changeZoomLevel(float delta) {
+ fZoomLevel += SkFloatToScalar(delta);
+ if (fZoomLevel > 0) {
+ fZoomLevel = SkMinScalar(fZoomLevel, MAX_ZOOM_LEVEL);
+ fZoomScale = fZoomLevel + SK_Scalar1;
+ } else if (fZoomLevel < 0) {
+ fZoomLevel = SkMaxScalar(fZoomLevel, MIN_ZOOM_LEVEL);
+ fZoomScale = SK_Scalar1 / (SK_Scalar1 - fZoomLevel);
+ } else {
+ fZoomScale = SK_Scalar1;
+ }
+ this->updateMatrix();
+}
+
+void SampleWindow::updateMatrix(){
+ SkMatrix m;
+ m.reset();
+ if (fZoomLevel) {
+ SkPoint center;
+ //m = this->getLocalMatrix();//.invert(&m);
+ m.mapXY(fZoomCenterX, fZoomCenterY, &center);
+ SkScalar cx = center.fX;
+ SkScalar cy = center.fY;
+
+ m.setTranslate(-cx, -cy);
+ m.postScale(fZoomScale, fZoomScale);
+ m.postTranslate(cx, cy);
+ }
+
+ if (fFlipAxis) {
+ m.preTranslate(fZoomCenterX, fZoomCenterY);
+ if (fFlipAxis & kFlipAxis_X) {
+ m.preScale(-SK_Scalar1, SK_Scalar1);
+ }
+ if (fFlipAxis & kFlipAxis_Y) {
+ m.preScale(SK_Scalar1, -SK_Scalar1);
+ }
+ m.preTranslate(-fZoomCenterX, -fZoomCenterY);
+ //canvas->concat(m);
+ }
+ // Apply any gesture matrix
+ m.preConcat(fGesture.localM());
+ m.preConcat(fGesture.globalM());
+
+ this->setLocalMatrix(m);
+
+ this->updateTitle();
+ this->inval(NULL);
+}
+bool SampleWindow::previousSample() {
+ fCurrIndex = (fCurrIndex - 1 + fSamples.count()) % fSamples.count();
+ this->loadView(create_transition(curr_view(this), (*fSamples[fCurrIndex])(),
+ fTransitionPrev));
+ return true;
+}
+
+bool SampleWindow::nextSample() {
+ fCurrIndex = (fCurrIndex + 1) % fSamples.count();
+ this->loadView(create_transition(curr_view(this), (*fSamples[fCurrIndex])(),
+ fTransitionNext));
+ return true;
+}
+
+bool SampleWindow::goToSample(int i) {
+ fCurrIndex = (i) % fSamples.count();
+ this->loadView(create_transition(curr_view(this),(*fSamples[fCurrIndex])(), 6));
+ return true;
+}
+
+SkString SampleWindow::getSampleTitle(int i) {
+ SkView* view = (*fSamples[i])();
+ SkString title;
+ SampleCode::RequestTitle(view, &title);
+ view->unref();
+ return title;
+}
+
+int SampleWindow::sampleCount() {
+ return fSamples.count();
+}
+
+void SampleWindow::showOverview() {
+ this->loadView(create_transition(curr_view(this),
+ create_overview(fSamples.count(), fSamples.begin()),
+ 4));
+}
+
+void SampleWindow::installDrawFilter(SkCanvas* canvas) {
+ canvas->setDrawFilter(new FlagsDrawFilter(fLCDState, fAAState,
+ fFilterState, fHintingState))->unref();
+}
+
+void SampleWindow::postAnimatingEvent() {
+ if (fAnimating) {
+ (new SkEvent(ANIMATING_EVENTTYPE, this->getSinkID()))->postDelay(ANIMATING_DELAY);
+ }
+}
+
+bool SampleWindow::onEvent(const SkEvent& evt) {
+ if (evt.isType(gUpdateWindowTitleEvtName)) {
+ this->updateTitle();
+ return true;
+ }
+ if (evt.isType(ANIMATING_EVENTTYPE)) {
+ if (fAnimating) {
+ this->nextSample();
+ this->postAnimatingEvent();
+ }
+ return true;
+ }
+ if (evt.isType("replace-transition-view")) {
+ this->loadView((SkView*)SkEventSink::FindSink(evt.getFast32()));
+ return true;
+ }
+ if (evt.isType("set-curr-index")) {
+ this->goToSample(evt.getFast32());
+ return true;
+ }
+ if (isInvalEvent(evt)) {
+ this->inval(NULL);
+ return true;
+ }
+ int selected = -1;
+ if (SkOSMenu::FindListIndex(evt, "Device Type", &selected)) {
+ this->setDeviceType((DeviceType)selected);
+ return true;
+ }
+ if (SkOSMenu::FindTriState(evt, "Pipe", &fPipeState)) {
+#ifdef PIPE_NET
+ if (!fPipeState != SkOSMenu::kOnState)
+ gServer.disconnectAll();
+#endif
+ (void)SampleView::SetUsePipe(curr_view(this), fPipeState);
+ this->updateTitle();
+ this->inval(NULL);
+ return true;
+ }
+ if (SkOSMenu::FindTriState(evt, "Tiling", &fTilingState)) {
+ int nx = 1, ny = 1;
+ switch (fTilingState) {
+ case SkOSMenu::kOffState: nx = 1; ny = 1; break;
+ case SkOSMenu::kMixedState: nx = 1; ny = 16; break;
+ case SkOSMenu::kOnState: nx = 4; ny = 4; break;
+ }
+ fTileCount.set(nx, ny);
+ this->inval(NULL);
+ return true;
+ }
+ if (SkOSMenu::FindSwitchState(evt, "Slide Show", NULL)) {
+ this->toggleSlideshow();
+ return true;
+ }
+ if (SkOSMenu::FindTriState(evt, "AA", &fAAState) ||
+ SkOSMenu::FindTriState(evt, "LCD", &fLCDState) ||
+ SkOSMenu::FindTriState(evt, "Filter", &fFilterState) ||
+ SkOSMenu::FindTriState(evt, "Hinting", &fHintingState) ||
+ SkOSMenu::FindSwitchState(evt, "Clip", &fUseClip) ||
+ SkOSMenu::FindSwitchState(evt, "Zoomer", &fShowZoomer) ||
+ SkOSMenu::FindSwitchState(evt, "Magnify", &fMagnify) ||
+ SkOSMenu::FindListIndex(evt, "Transition-Next", &fTransitionNext) ||
+ SkOSMenu::FindListIndex(evt, "Transition-Prev", &fTransitionPrev)) {
+ this->inval(NULL);
+ this->updateTitle();
+ return true;
+ }
+ if (SkOSMenu::FindSwitchState(evt, "Flip X", NULL)) {
+ fFlipAxis ^= kFlipAxis_X;
+ this->updateMatrix();
+ return true;
+ }
+ if (SkOSMenu::FindSwitchState(evt, "Flip Y", NULL)) {
+ fFlipAxis ^= kFlipAxis_Y;
+ this->updateMatrix();
+ return true;
+ }
+ if (SkOSMenu::FindAction(evt,"Save to PDF")) {
+ this->saveToPdf();
+ return true;
+ }
+#ifdef DEBUGGER
+ if (SkOSMenu::FindSwitchState(evt, "Debugger", &fDebugger)) {
+ if (fDebugger) {
+ fPipeState = SkOSMenu::kOnState;
+ (void)SampleView::SetUsePipe(curr_view(this), fPipeState);
+ } else {
+ this->loadView((*fSamples[fCurrIndex])());
+ }
+ this->inval(NULL);
+ return true;
+ }
+#endif
+ return this->INHERITED::onEvent(evt);
+}
+
+bool SampleWindow::onQuery(SkEvent* query) {
+ if (query->isType("get-slide-count")) {
+ query->setFast32(fSamples.count());
+ return true;
+ }
+ if (query->isType("get-slide-title")) {
+ SkView* view = (*fSamples[query->getFast32()])();
+ SkEvent evt(gTitleEvtName);
+ if (view->doQuery(&evt)) {
+ query->setString("title", evt.findString(gTitleEvtName));
+ }
+ SkSafeUnref(view);
+ return true;
+ }
+ if (query->isType("use-fast-text")) {
+ SkEvent evt(gFastTextEvtName);
+ return curr_view(this)->doQuery(&evt);
+ }
+ if (query->isType("ignore-window-bitmap")) {
+ query->setFast32(this->getGrContext() != NULL);
+ return true;
+ }
+ return this->INHERITED::onQuery(query);
+}
+
+#if 0 // UNUSED
+static void cleanup_for_filename(SkString* name) {
+ char* str = name->writable_str();
+ for (size_t i = 0; i < name->size(); i++) {
+ switch (str[i]) {
+ case ':': str[i] = '-'; break;
+ case '/': str[i] = '-'; break;
+ case ' ': str[i] = '_'; break;
+ default: break;
+ }
+ }
+}
+#endif
+
+//extern bool gIgnoreFastBlurRect;
+
+bool SampleWindow::onHandleChar(SkUnichar uni) {
+ {
+ SkView* view = curr_view(this);
+ if (view) {
+ SkEvent evt(gCharEvtName);
+ evt.setFast32(uni);
+ if (view->doQuery(&evt)) {
+ return true;
+ }
+ }
+ }
+
+ int dx = 0xFF;
+ int dy = 0xFF;
+
+ switch (uni) {
+ case '5': dx = 0; dy = 0; break;
+ case '8': dx = 0; dy = -1; break;
+ case '6': dx = 1; dy = 0; break;
+ case '2': dx = 0; dy = 1; break;
+ case '4': dx = -1; dy = 0; break;
+ case '7': dx = -1; dy = -1; break;
+ case '9': dx = 1; dy = -1; break;
+ case '3': dx = 1; dy = 1; break;
+ case '1': dx = -1; dy = 1; break;
+
+ default:
+ break;
+ }
+
+ if (0xFF != dx && 0xFF != dy) {
+ if ((dx | dy) == 0) {
+ fScrollTestX = fScrollTestY = 0;
+ } else {
+ fScrollTestX += dx;
+ fScrollTestY += dy;
+ }
+ this->inval(NULL);
+ return true;
+ }
+
+ switch (uni) {
+ case 'b':
+ {
+ postEventToSink(SkNEW_ARGS(SkEvent, ("PictFileView::toggleBBox")), curr_view(this));
+ this->updateTitle();
+ this->inval(NULL);
+ break;
+ }
+ case 'B':
+// gIgnoreFastBlurRect = !gIgnoreFastBlurRect;
+ this->inval(NULL);
+ break;
+
+ case 'f':
+ // only
+ toggleFPS();
+ break;
+ case 'g':
+ fRequestGrabImage = true;
+ this->inval(NULL);
+ break;
+ case 'G':
+ gShowGMBounds = !gShowGMBounds;
+ postEventToSink(GMSampleView::NewShowSizeEvt(gShowGMBounds),
+ curr_view(this));
+ this->inval(NULL);
+ break;
+ case 'i':
+ this->zoomIn();
+ break;
+ case 'o':
+ this->zoomOut();
+ break;
+ case 'r':
+ fRotate = !fRotate;
+ this->inval(NULL);
+ this->updateTitle();
+ return true;
+ case 'k':
+ fPerspAnim = !fPerspAnim;
+ this->inval(NULL);
+ this->updateTitle();
+ return true;
+#if SK_SUPPORT_GPU
+ case '\\':
+ this->setDeviceType(kNullGPU_DeviceType);
+ this->inval(NULL);
+ this->updateTitle();
+ return true;
+ case 'p':
+ {
+ GrContext* grContext = this->getGrContext();
+ if (grContext) {
+ size_t cacheBytes = grContext->getGpuTextureCacheBytes();
+ grContext->freeGpuResources();
+ SkDebugf("Purged %d bytes from the GPU resource cache.\n",
+ cacheBytes);
+ }
+ }
+ return true;
+#endif
+ case 's':
+ fScale = !fScale;
+ this->inval(NULL);
+ this->updateTitle();
+ return true;
+ default:
+ break;
+ }
+
+ if (fAppMenu->handleKeyEquivalent(uni)|| fSlideMenu->handleKeyEquivalent(uni)) {
+ this->onUpdateMenu(fAppMenu);
+ this->onUpdateMenu(fSlideMenu);
+ return true;
+ }
+ return this->INHERITED::onHandleChar(uni);
+}
+
+void SampleWindow::setDeviceType(DeviceType type) {
+ if (type == fDeviceType)
+ return;
+
+ fDevManager->tearDownBackend(this);
+
+ fDeviceType = type;
+
+ fDevManager->setUpBackend(this, fMSAASampleCount);
+
+ this->updateTitle();
+ this->inval(NULL);
+}
+
+void SampleWindow::toggleSlideshow() {
+ fAnimating = !fAnimating;
+ this->postAnimatingEvent();
+ this->updateTitle();
+}
+
+void SampleWindow::toggleRendering() {
+ this->setDeviceType(cycle_devicetype(fDeviceType));
+ this->updateTitle();
+ this->inval(NULL);
+}
+
+void SampleWindow::toggleFPS() {
+ fMeasureFPS = !fMeasureFPS;
+ this->updateTitle();
+ this->inval(NULL);
+}
+
+#include "SkDumpCanvas.h"
+
+bool SampleWindow::onHandleKey(SkKey key) {
+ {
+ SkView* view = curr_view(this);
+ if (view) {
+ SkEvent evt(gKeyEvtName);
+ evt.setFast32(key);
+ if (view->doQuery(&evt)) {
+ return true;
+ }
+ }
+ }
+ switch (key) {
+ case kRight_SkKey:
+ if (this->nextSample()) {
+ return true;
+ }
+ break;
+ case kLeft_SkKey:
+ if (this->previousSample()) {
+ return true;
+ }
+ return true;
+ case kUp_SkKey:
+ if (USE_ARROWS_FOR_ZOOM) {
+ this->changeZoomLevel(1.f);
+ } else {
+ fNClip = !fNClip;
+ this->inval(NULL);
+ this->updateTitle();
+ }
+ return true;
+ case kDown_SkKey:
+ if (USE_ARROWS_FOR_ZOOM) {
+ this->changeZoomLevel(-1.f);
+ } else {
+ this->setConfig(cycle_configs(this->getBitmap().config()));
+ this->updateTitle();
+ }
+ return true;
+ case kOK_SkKey: {
+ SkString title;
+ if (curr_title(this, &title)) {
+ writeTitleToPrefs(title.c_str());
+ }
+ return true;
+ }
+ case kBack_SkKey:
+ this->showOverview();
+ return true;
+ default:
+ break;
+ }
+ return this->INHERITED::onHandleKey(key);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static const char gGestureClickType[] = "GestureClickType";
+
+bool SampleWindow::onDispatchClick(int x, int y, Click::State state,
+ void* owner) {
+ if (Click::kMoved_State == state) {
+ updatePointer(x, y);
+ }
+ int w = SkScalarRound(this->width());
+ int h = SkScalarRound(this->height());
+
+ // check for the resize-box
+ if (w - x < 16 && h - y < 16) {
+ return false; // let the OS handle the click
+ }
+ else if (fMagnify) {
+ //it's only necessary to update the drawing if there's a click
+ this->inval(NULL);
+ return false; //prevent dragging while magnify is enabled
+ }
+ else {
+ return this->INHERITED::onDispatchClick(x, y, state, owner);
+ }
+}
+
+class GestureClick : public SkView::Click {
+public:
+ GestureClick(SkView* target) : SkView::Click(target) {
+ this->setType(gGestureClickType);
+ }
+
+ static bool IsGesture(Click* click) {
+ return click->isType(gGestureClickType);
+ }
+};
+
+SkView::Click* SampleWindow::onFindClickHandler(SkScalar x, SkScalar y) {
+ return new GestureClick(this);
+}
+
+bool SampleWindow::onClick(Click* click) {
+ if (GestureClick::IsGesture(click)) {
+ float x = static_cast<float>(click->fICurr.fX);
+ float y = static_cast<float>(click->fICurr.fY);
+
+ switch (click->fState) {
+ case SkView::Click::kDown_State:
+ fGesture.touchBegin(click->fOwner, x, y);
+ break;
+ case SkView::Click::kMoved_State:
+ fGesture.touchMoved(click->fOwner, x, y);
+ this->updateMatrix();
+ break;
+ case SkView::Click::kUp_State:
+ fGesture.touchEnd(click->fOwner);
+ this->updateMatrix();
+ break;
+ }
+ return true;
+ }
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SampleWindow::loadView(SkView* view) {
+ SkView::F2BIter iter(this);
+ SkView* prev = iter.next();
+ if (prev) {
+ prev->detachFromParent();
+ }
+
+ view->setVisibleP(true);
+ view->setClipToBounds(false);
+ this->attachChildToFront(view)->unref();
+ view->setSize(this->width(), this->height());
+
+ //repopulate the slide menu when a view is loaded
+ fSlideMenu->reset();
+#ifdef DEBUGGER
+ if (!is_debugger(view) && !is_overview(view) && !is_transition(view) && fDebugger) {
+ //Force Pipe to be on if using debugger
+ fPipeState = SkOSMenu::kOnState;
+ }
+#endif
+ (void)SampleView::SetUsePipe(view, fPipeState);
+ if (SampleView::IsSampleView(view))
+ ((SampleView*)view)->requestMenu(fSlideMenu);
+ this->onUpdateMenu(fSlideMenu);
+ this->updateTitle();
+}
+
+static const char* gConfigNames[] = {
+ "unknown config",
+ "A1",
+ "A8",
+ "Index8",
+ "565",
+ "4444",
+ "8888"
+};
+
+static const char* configToString(SkBitmap::Config c) {
+ return gConfigNames[c];
+}
+
+static const char* gDeviceTypePrefix[] = {
+ "raster: ",
+ "picture: ",
+#if SK_SUPPORT_GPU
+ "opengl: ",
+#if SK_ANGLE
+ "angle: ",
+#endif // SK_ANGLE
+ "null-gl: "
+#endif // SK_SUPPORT_GPU
+};
+SK_COMPILE_ASSERT(SK_ARRAY_COUNT(gDeviceTypePrefix) == SampleWindow::kDeviceTypeCnt,
+ array_size_mismatch);
+
+static const bool gDeviceTypeIsGPU[] = {
+ false,
+ false,
+#if SK_SUPPORT_GPU
+ true,
+#if SK_ANGLE
+ true,
+#endif // SK_ANGLE
+ true
+#endif // SK_SUPPORT_GPU
+};
+SK_COMPILE_ASSERT(SK_ARRAY_COUNT(gDeviceTypeIsGPU) == SampleWindow::kDeviceTypeCnt,
+ array_size_mismatch);
+
+
+static const char* trystate_str(SkOSMenu::TriState state,
+ const char trueStr[], const char falseStr[]) {
+ if (SkOSMenu::kOnState == state) {
+ return trueStr;
+ } else if (SkOSMenu::kOffState == state) {
+ return falseStr;
+ }
+ return NULL;
+}
+
+void SampleWindow::updateTitle() {
+ SkView* view = curr_view(this);
+
+ SkString title;
+ if (!curr_title(this, &title)) {
+ title.set("<unknown>");
+ }
+
+ title.prepend(gDeviceTypePrefix[fDeviceType]);
+
+ title.prepend(" ");
+ title.prepend(configToString(this->getBitmap().config()));
+
+ if (fAnimating) {
+ title.prepend("<A> ");
+ }
+ if (fScale) {
+ title.prepend("<S> ");
+ }
+ if (fRotate) {
+ title.prepend("<R> ");
+ }
+ if (fNClip) {
+ title.prepend("<C> ");
+ }
+ if (fPerspAnim) {
+ title.prepend("<K> ");
+ }
+
+ title.prepend(trystate_str(fLCDState, "LCD ", "lcd "));
+ title.prepend(trystate_str(fAAState, "AA ", "aa "));
+ title.prepend(trystate_str(fFilterState, "H ", "h "));
+ title.prepend(fFlipAxis & kFlipAxis_X ? "X " : NULL);
+ title.prepend(fFlipAxis & kFlipAxis_Y ? "Y " : NULL);
+
+ if (fZoomLevel) {
+ title.prependf("{%.2f} ", SkScalarToFloat(fZoomLevel));
+ }
+
+ if (fMeasureFPS) {
+ title.appendf(" %8.3f ms", fMeasureFPS_Time / (float)FPS_REPEAT_COUNT);
+ }
+ if (SampleView::IsSampleView(view)) {
+ switch (fPipeState) {
+ case SkOSMenu::kOnState:
+ title.prepend("<Pipe> ");
+ break;
+ case SkOSMenu::kMixedState:
+ title.prepend("<Tiled Pipe> ");
+ break;
+
+ default:
+ break;
+ }
+ title.prepend("! ");
+ }
+
+#if SK_SUPPORT_GPU
+ if (gDeviceTypeIsGPU[fDeviceType] &&
+ NULL != fDevManager &&
+ fDevManager->getGrRenderTarget()->numSamples() > 0) {
+ title.appendf(" [MSAA: %d]",
+ fDevManager->getGrRenderTarget()->numSamples());
+ }
+#endif
+
+ this->setTitle(title.c_str());
+}
+
+void SampleWindow::onSizeChange() {
+ this->INHERITED::onSizeChange();
+
+ SkView::F2BIter iter(this);
+ SkView* view = iter.next();
+ view->setSize(this->width(), this->height());
+
+ // rebuild our clippath
+ {
+ const SkScalar W = this->width();
+ const SkScalar H = this->height();
+
+ fClipPath.reset();
+#if 0
+ for (SkScalar y = SK_Scalar1; y < H; y += SkIntToScalar(32)) {
+ SkRect r;
+ r.set(SK_Scalar1, y, SkIntToScalar(30), y + SkIntToScalar(30));
+ for (; r.fLeft < W; r.offset(SkIntToScalar(32), 0))
+ fClipPath.addRect(r);
+ }
+#else
+ SkRect r;
+ r.set(0, 0, W, H);
+ fClipPath.addRect(r, SkPath::kCCW_Direction);
+ r.set(W/4, H/4, W*3/4, H*3/4);
+ fClipPath.addRect(r, SkPath::kCW_Direction);
+#endif
+ }
+
+ fZoomCenterX = SkScalarHalf(this->width());
+ fZoomCenterY = SkScalarHalf(this->height());
+
+#ifdef SK_BUILD_FOR_ANDROID
+ // FIXME: The first draw after a size change does not work on Android, so
+ // we post an invalidate.
+ this->postInvalDelay();
+#endif
+ this->updateTitle(); // to refresh our config
+ fDevManager->windowSizeChanged(this);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static const char is_sample_view_tag[] = "sample-is-sample-view";
+static const char repeat_count_tag[] = "sample-set-repeat-count";
+static const char set_use_pipe_tag[] = "sample-set-use-pipe";
+
+bool SampleView::IsSampleView(SkView* view) {
+ SkEvent evt(is_sample_view_tag);
+ return view->doQuery(&evt);
+}
+
+bool SampleView::SetRepeatDraw(SkView* view, int count) {
+ SkEvent evt(repeat_count_tag);
+ evt.setFast32(count);
+ return view->doEvent(evt);
+}
+
+bool SampleView::SetUsePipe(SkView* view, SkOSMenu::TriState state) {
+ SkEvent evt;
+ evt.setS32(set_use_pipe_tag, state);
+ return view->doEvent(evt);
+}
+
+bool SampleView::onEvent(const SkEvent& evt) {
+ if (evt.isType(repeat_count_tag)) {
+ fRepeatCount = evt.getFast32();
+ return true;
+ }
+ int32_t pipeHolder;
+ if (evt.findS32(set_use_pipe_tag, &pipeHolder)) {
+ fPipeState = static_cast<SkOSMenu::TriState>(pipeHolder);
+ return true;
+ }
+ return this->INHERITED::onEvent(evt);
+}
+
+bool SampleView::onQuery(SkEvent* evt) {
+ if (evt->isType(is_sample_view_tag)) {
+ return true;
+ }
+ return this->INHERITED::onQuery(evt);
+}
+
+
+class SimplePC : public SkGPipeController {
+public:
+ SimplePC(SkCanvas* target);
+ ~SimplePC();
+
+ virtual void* requestBlock(size_t minRequest, size_t* actual);
+ virtual void notifyWritten(size_t bytes);
+
+private:
+ SkGPipeReader fReader;
+ void* fBlock;
+ size_t fBlockSize;
+ size_t fBytesWritten;
+ int fAtomsWritten;
+ SkGPipeReader::Status fStatus;
+
+ size_t fTotalWritten;
+};
+
+SimplePC::SimplePC(SkCanvas* target) : fReader(target) {
+ fBlock = NULL;
+ fBlockSize = fBytesWritten = 0;
+ fStatus = SkGPipeReader::kDone_Status;
+ fTotalWritten = 0;
+ fAtomsWritten = 0;
+}
+
+SimplePC::~SimplePC() {
+// SkASSERT(SkGPipeReader::kDone_Status == fStatus);
+ if (fTotalWritten) {
+ SkDebugf("--- %d bytes %d atoms, status %d\n", fTotalWritten,
+ fAtomsWritten, fStatus);
+#ifdef PIPE_FILE
+ //File is open in append mode
+ FILE* f = fopen(FILE_PATH, "ab");
+ SkASSERT(f != NULL);
+ fwrite((const char*)fBlock + fBytesWritten, 1, bytes, f);
+ fclose(f);
+#endif
+#ifdef PIPE_NET
+ if (fAtomsWritten > 1 && fTotalWritten > 4) { //ignore done
+ gServer.acceptConnections();
+ gServer.writePacket(fBlock, fTotalWritten);
+ }
+#endif
+#ifdef DEBUGGER
+ gTempDataStore.reset();
+ gTempDataStore.append(fTotalWritten, (const char*)fBlock);
+#endif
+ }
+ sk_free(fBlock);
+}
+
+void* SimplePC::requestBlock(size_t minRequest, size_t* actual) {
+ sk_free(fBlock);
+
+ fBlockSize = minRequest * 4;
+ fBlock = sk_malloc_throw(fBlockSize);
+ fBytesWritten = 0;
+ *actual = fBlockSize;
+ return fBlock;
+}
+
+void SimplePC::notifyWritten(size_t bytes) {
+ SkASSERT(fBytesWritten + bytes <= fBlockSize);
+ fStatus = fReader.playback((const char*)fBlock + fBytesWritten, bytes);
+ SkASSERT(SkGPipeReader::kError_Status != fStatus);
+ fBytesWritten += bytes;
+ fTotalWritten += bytes;
+
+ fAtomsWritten += 1;
+}
+
+void SampleView::draw(SkCanvas* canvas) {
+ if (SkOSMenu::kOffState == fPipeState) {
+ this->INHERITED::draw(canvas);
+ } else {
+ SkGPipeWriter writer;
+ SimplePC controller(canvas);
+ TiledPipeController tc(canvas->getDevice()->accessBitmap(false),
+ &canvas->getTotalMatrix());
+ SkGPipeController* pc;
+ if (SkOSMenu::kMixedState == fPipeState) {
+ pc = &tc;
+ } else {
+ pc = &controller;
+ }
+ uint32_t flags = SkGPipeWriter::kCrossProcess_Flag;
+
+ canvas = writer.startRecording(pc, flags);
+ //Must draw before controller goes out of scope and sends data
+ this->INHERITED::draw(canvas);
+ //explicitly end recording to ensure writer is flushed before the memory
+ //is freed in the deconstructor of the controller
+ writer.endRecording();
+ }
+}
+void SampleView::onDraw(SkCanvas* canvas) {
+ this->onDrawBackground(canvas);
+
+ for (int i = 0; i < fRepeatCount; i++) {
+ SkAutoCanvasRestore acr(canvas, true);
+ this->onDrawContent(canvas);
+ }
+}
+
+void SampleView::onDrawBackground(SkCanvas* canvas) {
+ canvas->drawColor(fBGColor);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+template <typename T> void SkTBSort(T array[], int count) {
+ for (int i = 1; i < count - 1; i++) {
+ bool didSwap = false;
+ for (int j = count - 1; j > i; --j) {
+ if (array[j] < array[j-1]) {
+ T tmp(array[j-1]);
+ array[j-1] = array[j];
+ array[j] = tmp;
+ didSwap = true;
+ }
+ }
+ if (!didSwap) {
+ break;
+ }
+ }
+
+ for (int k = 0; k < count - 1; k++) {
+ SkASSERT(!(array[k+1] < array[k]));
+ }
+}
+
+#include "SkRandom.h"
+
+static void rand_rect(SkIRect* rect, SkRandom& rand) {
+ int bits = 8;
+ int shift = 32 - bits;
+ rect->set(rand.nextU() >> shift, rand.nextU() >> shift,
+ rand.nextU() >> shift, rand.nextU() >> shift);
+ rect->sort();
+}
+
+static void dumpRect(const SkIRect& r) {
+ SkDebugf(" { %d, %d, %d, %d },\n",
+ r.fLeft, r.fTop,
+ r.fRight, r.fBottom);
+}
+
+static void test_rects(const SkIRect rect[], int count) {
+ SkRegion rgn0, rgn1;
+
+ for (int i = 0; i < count; i++) {
+ rgn0.op(rect[i], SkRegion::kUnion_Op);
+ // dumpRect(rect[i]);
+ }
+ rgn1.setRects(rect, count);
+
+ if (rgn0 != rgn1) {
+ SkDebugf("\n");
+ for (int i = 0; i < count; i++) {
+ dumpRect(rect[i]);
+ }
+ SkDebugf("\n");
+ }
+}
+
+static void test() {
+ size_t i;
+
+ const SkIRect r0[] = {
+ { 0, 0, 1, 1 },
+ { 2, 2, 3, 3 },
+ };
+ const SkIRect r1[] = {
+ { 0, 0, 1, 3 },
+ { 1, 1, 2, 2 },
+ { 2, 0, 3, 3 },
+ };
+ const SkIRect r2[] = {
+ { 0, 0, 1, 2 },
+ { 2, 1, 3, 3 },
+ { 4, 0, 5, 1 },
+ { 6, 0, 7, 4 },
+ };
+
+ static const struct {
+ const SkIRect* fRects;
+ int fCount;
+ } gRecs[] = {
+ { r0, SK_ARRAY_COUNT(r0) },
+ { r1, SK_ARRAY_COUNT(r1) },
+ { r2, SK_ARRAY_COUNT(r2) },
+ };
+
+ for (i = 0; i < SK_ARRAY_COUNT(gRecs); i++) {
+ test_rects(gRecs[i].fRects, gRecs[i].fCount);
+ }
+
+ SkRandom rand;
+ for (i = 0; i < 10000; i++) {
+ SkRegion rgn0, rgn1;
+
+ const int N = 8;
+ SkIRect rect[N];
+ for (int j = 0; j < N; j++) {
+ rand_rect(&rect[j], rand);
+ }
+ test_rects(rect, N);
+ }
+}
+
+// FIXME: this should be in a header
+SkOSWindow* create_sk_window(void* hwnd, int argc, char** argv);
+SkOSWindow* create_sk_window(void* hwnd, int argc, char** argv) {
+ if (false) { // avoid bit rot, suppress warning
+ test();
+ }
+ return new SampleWindow(hwnd, argc, argv, NULL);
+}
+
+// FIXME: this should be in a header
+void get_preferred_size(int* x, int* y, int* width, int* height);
+void get_preferred_size(int* x, int* y, int* width, int* height) {
+ *x = 10;
+ *y = 50;
+ *width = 640;
+ *height = 480;
+}
+
+#ifdef SK_BUILD_FOR_IOS
+void save_args(int argc, char *argv[]) {
+}
+#endif
+
+// FIXME: this should be in a header
+void application_init();
+void application_init() {
+// setenv("ANDROID_ROOT", "../../../data", 0);
+#ifdef SK_BUILD_FOR_MAC
+ setenv("ANDROID_ROOT", "/android/device/data", 0);
+#endif
+ SkGraphics::Init();
+ SkEvent::Init();
+}
+
+// FIXME: this should be in a header
+void application_term();
+void application_term() {
+ SkEvent::Term();
+ SkGraphics::Term();
+}
diff --git a/samplecode/SampleApp.h b/samplecode/SampleApp.h
new file mode 100644
index 0000000000..2308a121ab
--- /dev/null
+++ b/samplecode/SampleApp.h
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2011 Skia
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SampleApp_DEFINED
+#define SampleApp_DEFINED
+
+#include "SkOSMenu.h"
+#include "SkPath.h"
+#include "SkScalar.h"
+#include "SkTDArray.h"
+#include "SkTouchGesture.h"
+#include "SkWindow.h"
+
+class GrContext;
+class GrRenderTarget;
+
+class SkCanvas;
+class SkData;
+class SkEvent;
+class SkPicture;
+class SkTypeface;
+class SkViewFactory;
+
+class SampleWindow : public SkOSWindow {
+ SkTDArray<const SkViewFactory*> fSamples;
+public:
+ enum DeviceType {
+ kRaster_DeviceType,
+ kPicture_DeviceType,
+#if SK_SUPPORT_GPU
+ kGPU_DeviceType,
+#if SK_ANGLE
+ kANGLE_DeviceType,
+#endif // SK_ANGLE
+ kNullGPU_DeviceType,
+#endif // SK_SUPPORT_GPU
+
+ kDeviceTypeCnt
+ };
+ /**
+ * SampleApp ports can subclass this manager class if they want to:
+ * * filter the types of devices supported
+ * * customize plugging of SkDevice objects into an SkCanvas
+ * * customize publishing the results of draw to the OS window
+ * * manage GrContext / GrRenderTarget lifetimes
+ */
+ class DeviceManager : public SkRefCnt {
+ public:
+ SK_DECLARE_INST_COUNT(DeviceManager)
+
+ virtual void setUpBackend(SampleWindow* win, int msaaSampleCount) = 0;
+
+ virtual void tearDownBackend(SampleWindow* win) = 0;
+
+ // called before drawing. should install correct device
+ // type on the canvas. Will skip drawing if returns false.
+ virtual SkCanvas* createCanvas(DeviceType dType, SampleWindow* win) = 0;
+
+ // called after drawing, should get the results onto the
+ // screen.
+ virtual void publishCanvas(DeviceType dType,
+ SkCanvas* canvas,
+ SampleWindow* win) = 0;
+
+ // called when window changes size, guaranteed to be called
+ // at least once before first draw (after init)
+ virtual void windowSizeChanged(SampleWindow* win) = 0;
+
+ // return the GrContext backing gpu devices (NULL if not built with GPU support)
+ virtual GrContext* getGrContext() = 0;
+
+ // return the GrRenderTarget backing gpu devices (NULL if not built with GPU support)
+ virtual GrRenderTarget* getGrRenderTarget() = 0;
+ private:
+ typedef SkRefCnt INHERITED;
+ };
+
+ SampleWindow(void* hwnd, int argc, char** argv, DeviceManager*);
+ virtual ~SampleWindow();
+
+ virtual SkCanvas* createCanvas() SK_OVERRIDE {
+ SkCanvas* canvas = NULL;
+ if (fDevManager) {
+ canvas = fDevManager->createCanvas(fDeviceType, this);
+ }
+ if (NULL == canvas) {
+ canvas = this->INHERITED::createCanvas();
+ }
+ return canvas;
+ }
+
+ virtual void draw(SkCanvas* canvas);
+
+ void setDeviceType(DeviceType type);
+ void toggleRendering();
+ void toggleSlideshow();
+ void toggleFPS();
+ void showOverview();
+
+ GrContext* getGrContext() const { return fDevManager->getGrContext(); }
+
+ void setZoomCenter(float x, float y);
+ void changeZoomLevel(float delta);
+ bool nextSample();
+ bool previousSample();
+ bool goToSample(int i);
+ SkString getSampleTitle(int i);
+ int sampleCount();
+ bool handleTouch(int ownerId, float x, float y,
+ SkView::Click::State state);
+ void saveToPdf();
+ SkData* getPDFData() { return fPDFData; }
+ void postInvalDelay();
+
+ DeviceType getDeviceType() const { return fDeviceType; }
+
+protected:
+ virtual void onDraw(SkCanvas* canvas);
+ virtual bool onHandleKey(SkKey key);
+ virtual bool onHandleChar(SkUnichar);
+ virtual void onSizeChange();
+
+ virtual SkCanvas* beforeChildren(SkCanvas*);
+ virtual void afterChildren(SkCanvas*);
+ virtual void beforeChild(SkView* child, SkCanvas* canvas);
+ virtual void afterChild(SkView* child, SkCanvas* canvas);
+
+ virtual bool onEvent(const SkEvent& evt);
+ virtual bool onQuery(SkEvent* evt);
+
+ virtual bool onDispatchClick(int x, int y, Click::State, void* owner);
+ virtual bool onClick(Click* click);
+ virtual Click* onFindClickHandler(SkScalar x, SkScalar y);
+
+ void registerPictFileSamples(char** argv, int argc);
+ void registerPictFileSample(char** argv, int argc);
+
+private:
+ class DefaultDeviceManager;
+
+ int fCurrIndex;
+
+ SkPicture* fPicture;
+ SkPath fClipPath;
+
+ SkTouchGesture fGesture;
+ SkScalar fZoomLevel;
+ SkScalar fZoomScale;
+
+ DeviceType fDeviceType;
+ DeviceManager* fDevManager;
+
+ bool fSaveToPdf;
+ SkCanvas* fPdfCanvas;
+ SkData* fPDFData;
+
+ bool fUseClip;
+ bool fNClip;
+ bool fAnimating;
+ bool fRotate;
+ bool fPerspAnim;
+ SkScalar fPerspAnimTime;
+ bool fScale;
+ bool fRequestGrabImage;
+ bool fMeasureFPS;
+ SkMSec fMeasureFPS_Time;
+ SkMSec fMeasureFPS_StartTime;
+ bool fMagnify;
+ SkISize fTileCount;
+
+
+ SkOSMenu::TriState fPipeState; // Mixed uses a tiled pipe
+ // On uses a normal pipe
+ // Off uses no pipe
+ int fUsePipeMenuItemID;
+ bool fDebugger;
+
+ // The following are for the 'fatbits' drawing
+ // Latest position of the mouse.
+ int fMouseX, fMouseY;
+ int fFatBitsScale;
+ // Used by the text showing position and color values.
+ SkTypeface* fTypeface;
+ bool fShowZoomer;
+
+ SkOSMenu::TriState fLCDState;
+ SkOSMenu::TriState fAAState;
+ SkOSMenu::TriState fFilterState;
+ SkOSMenu::TriState fHintingState;
+ SkOSMenu::TriState fTilingState;
+ unsigned fFlipAxis;
+
+ int fMSAASampleCount;
+
+ int fScrollTestX, fScrollTestY;
+ SkScalar fZoomCenterX, fZoomCenterY;
+
+ //Stores global settings
+ SkOSMenu* fAppMenu; // We pass ownership to SkWindow, when we call addMenu
+ //Stores slide specific settings
+ SkOSMenu* fSlideMenu; // We pass ownership to SkWindow, when we call addMenu
+
+ int fTransitionNext;
+ int fTransitionPrev;
+
+ void loadView(SkView*);
+ void updateTitle();
+
+ bool zoomIn();
+ bool zoomOut();
+ void updatePointer(int x, int y);
+ void magnify(SkCanvas* canvas);
+ void showZoomer(SkCanvas* canvas);
+ void updateMatrix();
+ void postAnimatingEvent();
+ void installDrawFilter(SkCanvas*);
+ int findByTitle(const char*);
+ void listTitles();
+
+ typedef SkOSWindow INHERITED;
+};
+
+#endif
diff --git a/samplecode/SampleColorFilter.cpp b/samplecode/SampleColorFilter.cpp
new file mode 100644
index 0000000000..e57ccb0d43
--- /dev/null
+++ b/samplecode/SampleColorFilter.cpp
@@ -0,0 +1,219 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SampleCode.h"
+#include "SkView.h"
+#include "SkCanvas.h"
+#include "SkColorFilter.h"
+#include "SkDevice.h"
+#include "SkPaint.h"
+#include "SkShader.h"
+
+static int inflate5To8(int x) {
+ return (x << 3) | (x >> 2);
+}
+
+static int trunc5(int x) {
+ return x >> 3;
+}
+
+#define SK_R16_BITS 5
+
+static int round5_slow(int x) {
+ int orig = x & 7;
+ int fake = x >> 5;
+ int trunc = x >> 3;
+
+ int diff = fake - orig;
+
+ int bias = 0;
+ if (diff > 4) {
+ bias = -1;
+ } else if (diff < -4) {
+ bias = 1;
+ }
+ return trunc + bias;
+}
+
+static int round5_fast(int x) {
+ int result = x + 3 - (x >> 5) + (x >> 7);
+ result >>= 3;
+
+ {
+ int r2 = round5_slow(x);
+ SkASSERT(r2 == result);
+ }
+ return result;
+}
+
+static void test_5bits() {
+ int e0 = 0;
+ int e1 = 0;
+ int e2 = 0;
+ int ae0 = 0;
+ int ae1 = 0;
+ int ae2 = 0;
+ for (int i = 0; i < 256; i++) {
+ int t0 = trunc5(i);
+ int t1 = round5_fast(i);
+ int t2 = trunc5(i);
+ int v0 = inflate5To8(t0);
+ int v1 = inflate5To8(t1);
+ int v2 = inflate5To8(t2);
+ int err0 = i - v0;
+ int err1 = i - v1;
+ int err2 = i - v2;
+ SkDebugf("--- %3d : trunc=%3d (%2d) round:%3d (%2d) \n"/*new:%d (%2d)\n"*/, i,
+ v0, err0, v1, err1, v2, err2);
+
+
+ e0 += err0;
+ e1 += err1;
+ e2 += err2;
+ ae0 += SkAbs32(err0);
+ ae1 += SkAbs32(err1);
+ ae2 += SkAbs32(err2);
+ }
+ SkDebugf("--- trunc: %d %d round: %d %d new: %d %d\n", e0, ae0, e1, ae1, e2, ae2);
+}
+
+static SkShader* createChecker() {
+ SkBitmap bm;
+ bm.setConfig(SkBitmap::kARGB_8888_Config, 2, 2);
+ bm.allocPixels();
+ bm.lockPixels();
+ *bm.getAddr32(0, 0) = *bm.getAddr32(1, 1) = SkPreMultiplyColor(0xFFFFFFFF);
+ *bm.getAddr32(0, 1) = *bm.getAddr32(1, 0) = SkPreMultiplyColor(0xFFCCCCCC);
+ SkShader* s = SkShader::CreateBitmapShader(bm, SkShader::kRepeat_TileMode,
+ SkShader::kRepeat_TileMode);
+
+ SkMatrix m;
+ m.setScale(12, 12);
+ s->setLocalMatrix(m);
+ return s;
+}
+
+static SkBitmap createBitmap(int n) {
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config, n, n);
+ bitmap.allocPixels();
+ bitmap.eraseColor(SK_ColorTRANSPARENT);
+
+ SkCanvas canvas(bitmap);
+ SkRect r;
+ r.set(0, 0, SkIntToScalar(n), SkIntToScalar(n));
+ r.inset(SK_Scalar1, SK_Scalar1);
+
+ SkPaint paint;
+ paint.setAntiAlias(true);
+
+ paint.setColor(SK_ColorRED);
+ canvas.drawOval(r, paint);
+
+ r.inset(SK_Scalar1*n/4, SK_Scalar1*n/4);
+ paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+ paint.setColor(0x800000FF);
+ canvas.drawOval(r, paint);
+
+ return bitmap;
+}
+
+class ColorFilterView : public SampleView {
+ SkBitmap fBitmap;
+ SkShader* fShader;
+ enum {
+ N = 64
+ };
+public:
+ ColorFilterView() {
+ fBitmap = createBitmap(N);
+ fShader = createChecker();
+
+ if (false) { // avoid bit rot, suppress warning
+ test_5bits();
+ }
+ }
+
+ virtual ~ColorFilterView() {
+ fShader->unref();
+ }
+
+protected:
+ // overrides from SkEventSink
+ virtual bool onQuery(SkEvent* evt) {
+ if (SampleCode::TitleQ(*evt)) {
+ SampleCode::TitleR(evt, "ColorFilter");
+ return true;
+ }
+ return this->INHERITED::onQuery(evt);
+ }
+
+ virtual void onDrawBackground(SkCanvas* canvas) {
+ SkPaint paint;
+ paint.setShader(fShader);
+ canvas->drawPaint(paint);
+ }
+
+ virtual void onDrawContent(SkCanvas* canvas) {
+ if (false) {
+ SkPaint p;
+ p.setAntiAlias(true);
+ SkRect r = { 20.4f, 10, 20.6f, 20 };
+ canvas->drawRect(r, p);
+ r.set(30.9f, 10, 31.1f, 20);
+ canvas->drawRect(r, p);
+ return;
+ }
+
+ static const SkXfermode::Mode gModes[] = {
+ SkXfermode::kClear_Mode,
+ SkXfermode::kSrc_Mode,
+ SkXfermode::kDst_Mode,
+ SkXfermode::kSrcOver_Mode,
+ SkXfermode::kDstOver_Mode,
+ SkXfermode::kSrcIn_Mode,
+ SkXfermode::kDstIn_Mode,
+ SkXfermode::kSrcOut_Mode,
+ SkXfermode::kDstOut_Mode,
+ SkXfermode::kSrcATop_Mode,
+ SkXfermode::kDstATop_Mode,
+ SkXfermode::kXor_Mode,
+ SkXfermode::kPlus_Mode,
+ SkXfermode::kMultiply_Mode,
+ };
+
+ static const SkColor gColors[] = {
+ 0xFF000000,
+ 0x80000000,
+ 0xFF00FF00,
+ 0x8000FF00,
+ 0x00000000,
+ };
+
+ float scale = 1.5f;
+ SkPaint paint;
+ canvas->translate(N / 8, N / 8);
+
+ for (size_t y = 0; y < SK_ARRAY_COUNT(gColors); y++) {
+ for (size_t x = 0; x < SK_ARRAY_COUNT(gModes); x++) {
+ SkColorFilter* cf = SkColorFilter::CreateModeFilter(gColors[y], gModes[x]);
+ SkSafeUnref(paint.setColorFilter(cf));
+ canvas->drawBitmap(fBitmap, x * N * 1.25f, y * N * scale, &paint);
+ }
+ }
+
+ }
+
+private:
+ typedef SampleView INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static SkView* MyFactory() { return new ColorFilterView; }
+static SkViewRegister reg(MyFactory);
+
diff --git a/samplecode/SampleDither.cpp b/samplecode/SampleDither.cpp
new file mode 100644
index 0000000000..259bc94627
--- /dev/null
+++ b/samplecode/SampleDither.cpp
@@ -0,0 +1,185 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SampleCode.h"
+#include "SkView.h"
+#include "SkCanvas.h"
+#include "SkGradientShader.h"
+#include "SkPath.h"
+#include "SkRegion.h"
+#include "SkShader.h"
+#include "SkUtils.h"
+#include "Sk1DPathEffect.h"
+#include "SkCornerPathEffect.h"
+#include "SkPathMeasure.h"
+#include "SkRandom.h"
+#include "SkColorPriv.h"
+#include "SkColorFilter.h"
+#include "SkDither.h"
+
+static void draw_sweep(SkCanvas* c, int width, int height, SkScalar angle) {
+ SkRect r;
+ SkPaint p;
+
+ p.setAntiAlias(true);
+// p.setDither(true);
+ p.setStrokeWidth(SkIntToScalar(width/10));
+ p.setStyle(SkPaint::kStroke_Style);
+
+ r.set(0, 0, SkIntToScalar(width), SkIntToScalar(height));
+
+ // SkColor colors[] = { SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN, SK_ColorCYAN };
+ SkColor colors[] = { 0x4c737373, 0x4c737373, 0xffffd300 };
+ SkShader* s = SkGradientShader::CreateSweep(r.centerX(), r.centerY(),
+ colors, NULL, SK_ARRAY_COUNT(colors));
+ p.setShader(s)->unref();
+
+ SkAutoCanvasRestore acr(c, true);
+
+ c->translate(r.centerX(), r.centerY());
+ c->rotate(angle);
+ c->translate(-r.centerX(), -r.centerY());
+
+ SkRect bounds = r;
+ r.inset(p.getStrokeWidth(), p.getStrokeWidth());
+ SkRect innerBounds = r;
+
+ if (true) {
+ c->drawOval(r, p);
+ } else {
+ SkScalar x = r.centerX();
+ SkScalar y = r.centerY();
+ SkScalar radius = r.width() / 2;
+ SkScalar thickness = p.getStrokeWidth();
+ SkScalar sweep = SkFloatToScalar(360.0f);
+ SkPath path;
+
+ path.moveTo(x + radius, y);
+ // outer top
+ path.lineTo(x + radius + thickness, y);
+ // outer arc
+ path.arcTo(bounds, 0, sweep, false);
+ // inner arc
+ path.arcTo(innerBounds, sweep, -sweep, false);
+ path.close();
+ }
+}
+
+static void make_bm(SkBitmap* bm) {
+ bm->setConfig(SkBitmap::kARGB_8888_Config, 100, 100);
+ bm->allocPixels();
+#if 0
+ bm->eraseColor(SK_ColorBLUE);
+ return;
+#else
+ bm->eraseColor(SK_ColorTRANSPARENT);
+#endif
+
+ SkCanvas c(*bm);
+ draw_sweep(&c, bm->width(), bm->height(), 0);
+}
+
+static void pre_dither(const SkBitmap& bm) {
+ SkAutoLockPixels alp(bm);
+
+ for (int y = 0; y < bm.height(); y++) {
+ DITHER_4444_SCAN(y);
+
+ SkPMColor* p = bm.getAddr32(0, y);
+ for (int x = 0; x < bm.width(); x++) {
+ SkPMColor c = *p;
+
+ unsigned a = SkGetPackedA32(c);
+ unsigned r = SkGetPackedR32(c);
+ unsigned g = SkGetPackedG32(c);
+ unsigned b = SkGetPackedB32(c);
+
+ unsigned d = DITHER_VALUE(x);
+
+ a = SkDITHER_A32To4444(a, d);
+ r = SkDITHER_R32To4444(r, d);
+ g = SkDITHER_G32To4444(g, d);
+ b = SkDITHER_B32To4444(b, d);
+
+ a = SkA4444ToA32(a);
+ r = SkR4444ToR32(r);
+ g = SkG4444ToG32(g);
+ b = SkB4444ToB32(b);
+
+ *p++ = SkPackARGB32(a, r, g, b);
+ }
+ }
+}
+
+class DitherView : public SampleView {
+public:
+ SkBitmap fBM, fBMPreDither, fBM16;
+ SkScalar fAngle;
+
+ DitherView() {
+ make_bm(&fBM);
+ make_bm(&fBMPreDither);
+ pre_dither(fBMPreDither);
+ fBM.copyTo(&fBM16, SkBitmap::kARGB_4444_Config);
+
+ fAngle = 0;
+
+ this->setBGColor(0xFF181818);
+ }
+
+protected:
+ // overrides from SkEventSink
+ virtual bool onQuery(SkEvent* evt) {
+ if (SampleCode::TitleQ(*evt)) {
+ SampleCode::TitleR(evt, "Dither");
+ return true;
+ }
+ return this->INHERITED::onQuery(evt);
+ }
+
+ virtual void onDrawContent(SkCanvas* canvas) {
+ SkPaint paint;
+ SkScalar x = SkIntToScalar(10);
+ SkScalar y = SkIntToScalar(10);
+ const SkScalar DX = SkIntToScalar(fBM.width() + 10);
+
+ paint.setAntiAlias(true);
+
+ if (true) {
+ canvas->drawBitmap(fBM, x, y, &paint);
+ x += DX;
+ paint.setDither(true);
+ canvas->drawBitmap(fBM, x, y, &paint);
+
+ x += DX;
+ paint.setDither(false);
+ canvas->drawBitmap(fBMPreDither, x, y, &paint);
+
+ x += DX;
+ canvas->drawBitmap(fBM16, x, y, &paint);
+ }
+
+ canvas->translate(DX, DX*2);
+ draw_sweep(canvas, fBM.width(), fBM.height(), fAngle);
+ canvas->translate(DX, 0);
+ draw_sweep(canvas, fBM.width()>>1, fBM.height()>>1, fAngle);
+ canvas->translate(DX, 0);
+ draw_sweep(canvas, fBM.width()>>2, fBM.height()>>2, fAngle);
+
+ fAngle += SK_Scalar1/2;
+ this->inval(NULL);
+ }
+
+private:
+ typedef SampleView INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static SkView* MyFactory() { return new DitherView; }
+static SkViewRegister reg(MyFactory);
+
diff --git a/samplecode/SampleFatBits.cpp b/samplecode/SampleFatBits.cpp
new file mode 100644
index 0000000000..29fd8651a7
--- /dev/null
+++ b/samplecode/SampleFatBits.cpp
@@ -0,0 +1,459 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SampleCode.h"
+#include "SkView.h"
+#include "SkCanvas.h"
+#include "SkPath.h"
+#include "SkRegion.h"
+#include "SkShader.h"
+#include "SkUtils.h"
+#include "SkImage.h"
+#include "SkSurface.h"
+
+#define FAT_PIXEL_COLOR SK_ColorBLACK
+#define PIXEL_CENTER_SIZE 3
+#define WIRE_FRAME_COLOR 0xFFFF0000 /*0xFF00FFFF*/
+#define WIRE_FRAME_SIZE 1.5f
+
+static void erase(SkSurface* surface) {
+ surface->getCanvas()->clear(SK_ColorTRANSPARENT);
+}
+
+static SkShader* createChecker() {
+// SkColor colors[] = { 0xFFFDFDFD, 0xFFF4F4F4 };
+ SkColor colors[] = { 0xFFFFFFFF, 0xFFFFFFFF };
+ SkBitmap bm;
+ bm.setConfig(SkBitmap::kARGB_8888_Config, 2, 2);
+ bm.allocPixels();
+ bm.lockPixels();
+ *bm.getAddr32(0, 0) = *bm.getAddr32(1, 1) = SkPreMultiplyColor(colors[0]);
+ *bm.getAddr32(0, 1) = *bm.getAddr32(1, 0) = SkPreMultiplyColor(colors[1]);
+ SkShader* s = SkShader::CreateBitmapShader(bm, SkShader::kRepeat_TileMode,
+ SkShader::kRepeat_TileMode);
+
+ SkMatrix m;
+ m.setScale(12, 12);
+ s->setLocalMatrix(m);
+ return s;
+}
+
+class FatBits {
+public:
+ FatBits() : fShader(createChecker()) {
+ fAA = false;
+ fStyle = kHair_Style;
+ fGrid = true;
+ fShowSkeleton = true;
+ fUseGPU = false;
+ fUseClip = false;
+
+ fClipRect.set(2, 2, 11, 8 );
+ }
+
+ int getZoom() const { return fZoom; }
+
+ bool getAA() const { return fAA; }
+ void setAA(bool aa) { fAA = aa; }
+
+ bool getGrid() const { return fGrid; }
+ void setGrid(bool g) { fGrid = g; }
+
+ bool getShowSkeleton() const { return fShowSkeleton; }
+ void setShowSkeleton(bool ss) { fShowSkeleton = ss; }
+
+ bool getUseGPU() const { return fUseGPU; }
+ void setUseGPU(bool ug) { fUseGPU = ug; }
+
+ bool getUseClip() const { return fUseClip; }
+ void setUseClip(bool uc) { fUseClip = uc; }
+
+ enum Style {
+ kHair_Style,
+ kStroke_Style,
+ };
+ Style getStyle() const { return fStyle; }
+ void setStyle(Style s) { fStyle = s; }
+
+ void setWHZ(int width, int height, int zoom) {
+ fW = width;
+ fH = height;
+ fZoom = zoom;
+ fBounds.set(0, 0, SkIntToScalar(width * zoom), SkIntToScalar(height * zoom));
+ fMatrix.setScale(SkIntToScalar(zoom), SkIntToScalar(zoom));
+ fInverse.setScale(SK_Scalar1 / zoom, SK_Scalar1 / zoom);
+ fShader->setLocalMatrix(fMatrix);
+
+ SkImage::Info info = {
+ width, height, SkImage::kPMColor_ColorType, SkImage::kPremul_AlphaType
+ };
+ fMinSurface.reset(SkSurface::NewRaster(info));
+ info.fWidth *= zoom;
+ info.fHeight *= zoom;
+ fMaxSurface.reset(SkSurface::NewRaster(info));
+ }
+
+ void drawBG(SkCanvas*);
+ void drawFG(SkCanvas*);
+ void drawLine(SkCanvas*, SkPoint pts[2]);
+ void drawRect(SkCanvas* canvas, SkPoint pts[2]);
+
+private:
+ bool fAA, fGrid, fShowSkeleton, fUseGPU, fUseClip;
+ Style fStyle;
+ int fW, fH, fZoom;
+ SkMatrix fMatrix, fInverse;
+ SkRect fBounds, fClipRect;
+ SkAutoTUnref<SkShader> fShader;
+ SkAutoTUnref<SkSurface> fMinSurface;
+ SkAutoTUnref<SkSurface> fMaxSurface;
+
+ void setupPaint(SkPaint* paint) {
+ bool aa = this->getAA();
+ switch (fStyle) {
+ case kHair_Style:
+ paint->setStrokeWidth(0);
+ break;
+ case kStroke_Style:
+ paint->setStrokeWidth(SK_Scalar1);
+// paint->setStrokeWidth(SK_Scalar1 + SK_Scalar1/500);
+ break;
+ }
+ paint->setAntiAlias(aa);
+ }
+
+ void setupSkeletonPaint(SkPaint* paint) {
+ paint->setStyle(SkPaint::kStroke_Style);
+ paint->setStrokeWidth(WIRE_FRAME_SIZE);
+ paint->setColor(fShowSkeleton ? WIRE_FRAME_COLOR : 0);
+ paint->setAntiAlias(true);
+ }
+
+ void drawLineSkeleton(SkCanvas* max, const SkPoint pts[]);
+ void drawRectSkeleton(SkCanvas* max, const SkRect& r) {
+ SkPaint paint;
+ this->setupSkeletonPaint(&paint);
+ SkPath path;
+
+ if (fUseGPU && fAA) {
+ SkRect rr = r;
+ rr.inset(SkIntToScalar(fZoom)/2, SkIntToScalar(fZoom)/2);
+ path.addRect(rr);
+ path.moveTo(rr.fLeft, rr.fTop);
+ path.lineTo(rr.fRight, rr.fBottom);
+ rr = r;
+ rr.inset(-SkIntToScalar(fZoom)/2, -SkIntToScalar(fZoom)/2);
+ path.addRect(rr);
+ } else {
+ path.addRect(r);
+ if (fUseGPU) {
+ path.moveTo(r.fLeft, r.fTop);
+ path.lineTo(r.fRight, r.fBottom);
+ }
+ }
+ max->drawPath(path, paint);
+ }
+
+ void copyMinToMax() {
+ erase(fMaxSurface);
+ SkCanvas* canvas = fMaxSurface->getCanvas();
+ canvas->save();
+ canvas->concat(fMatrix);
+ fMinSurface->draw(canvas, 0, 0, NULL);
+ canvas->restore();
+
+ SkPaint paint;
+ paint.setXfermodeMode(SkXfermode::kClear_Mode);
+ for (int iy = 1; iy < fH; ++iy) {
+ SkScalar y = SkIntToScalar(iy * fZoom);
+ canvas->drawLine(0, y - SK_ScalarHalf, 999, y - SK_ScalarHalf, paint);
+ }
+ for (int ix = 1; ix < fW; ++ix) {
+ SkScalar x = SkIntToScalar(ix * fZoom);
+ canvas->drawLine(x - SK_ScalarHalf, 0, x - SK_ScalarHalf, 999, paint);
+ }
+ }
+};
+
+void FatBits::drawBG(SkCanvas* canvas) {
+ SkPaint paint;
+
+ paint.setShader(fShader);
+ canvas->drawRect(fBounds, paint);
+ paint.setShader(NULL);
+}
+
+void FatBits::drawFG(SkCanvas* canvas) {
+ SkPaint inner, outer;
+
+ inner.setAntiAlias(true);
+ inner.setColor(SK_ColorBLACK);
+ inner.setStrokeWidth(PIXEL_CENTER_SIZE);
+
+ outer.setAntiAlias(true);
+ outer.setColor(SK_ColorWHITE);
+ outer.setStrokeWidth(PIXEL_CENTER_SIZE + 2);
+
+ SkScalar half = SkIntToScalar(fZoom) / 2;
+ for (int iy = 0; iy < fH; ++iy) {
+ SkScalar y = SkIntToScalar(iy * fZoom) + half;
+ for (int ix = 0; ix < fW; ++ix) {
+ SkScalar x = SkIntToScalar(ix * fZoom) + half;
+
+ canvas->drawPoint(x, y, outer);
+ canvas->drawPoint(x, y, inner);
+ }
+ }
+
+ if (fUseClip) {
+ SkPaint p;
+ p.setStyle(SkPaint::kStroke_Style);
+ p.setColor(SK_ColorLTGRAY);
+ SkRect r = {
+ fClipRect.fLeft * fZoom,
+ fClipRect.fTop * fZoom,
+ fClipRect.fRight * fZoom,
+ fClipRect.fBottom * fZoom
+ };
+ canvas->drawRect(r, p);
+ }
+}
+
+void FatBits::drawLineSkeleton(SkCanvas* max, const SkPoint pts[]) {
+ SkPaint paint;
+ this->setupSkeletonPaint(&paint);
+
+ SkPath path;
+ path.moveTo(pts[0]);
+ path.lineTo(pts[1]);
+
+ switch (fStyle) {
+ case kHair_Style:
+ if (fUseGPU) {
+ SkPaint p;
+ p.setStyle(SkPaint::kStroke_Style);
+ p.setStrokeWidth(SK_Scalar1 * fZoom);
+ SkPath dst;
+ p.getFillPath(path, &dst);
+ path.addPath(dst);
+ }
+ break;
+ case kStroke_Style: {
+ SkPaint p;
+ p.setStyle(SkPaint::kStroke_Style);
+ p.setStrokeWidth(SK_Scalar1 * fZoom);
+ SkPath dst;
+ p.getFillPath(path, &dst);
+ path = dst;
+
+ if (fUseGPU) {
+ path.moveTo(dst.getPoint(0));
+ path.lineTo(dst.getPoint(2));
+ }
+ } break;
+ }
+ max->drawPath(path, paint);
+}
+
+void FatBits::drawLine(SkCanvas* canvas, SkPoint pts[]) {
+ SkPaint paint;
+
+ fInverse.mapPoints(pts, 2);
+
+ if (fGrid) {
+ SkScalar dd = 0;//SK_Scalar1 / 50;
+ pts[0].set(SkScalarRoundToScalar(pts[0].fX) + dd,
+ SkScalarRoundToScalar(pts[0].fY) + dd);
+ pts[1].set(SkScalarRoundToScalar(pts[1].fX) + dd,
+ SkScalarRoundToScalar(pts[1].fY) + dd);
+ }
+
+ erase(fMinSurface);
+ this->setupPaint(&paint);
+ paint.setColor(FAT_PIXEL_COLOR);
+ if (fUseClip) {
+ fMinSurface->getCanvas()->save();
+ SkRect r = fClipRect;
+ r.inset(SK_Scalar1/3, SK_Scalar1/3);
+ fMinSurface->getCanvas()->clipRect(r, SkRegion::kIntersect_Op, true);
+ }
+ fMinSurface->getCanvas()->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint);
+ if (fUseClip) {
+ fMinSurface->getCanvas()->restore();
+ }
+ this->copyMinToMax();
+
+ SkCanvas* max = fMaxSurface->getCanvas();
+
+ fMatrix.mapPoints(pts, 2);
+ this->drawLineSkeleton(max, pts);
+
+ fMaxSurface->draw(canvas, 0, 0, NULL);
+}
+
+void FatBits::drawRect(SkCanvas* canvas, SkPoint pts[2]) {
+ SkPaint paint;
+
+ fInverse.mapPoints(pts, 2);
+
+ if (fGrid) {
+ pts[0].set(SkScalarRoundToScalar(pts[0].fX), SkScalarRoundToScalar(pts[0].fY));
+ pts[1].set(SkScalarRoundToScalar(pts[1].fX), SkScalarRoundToScalar(pts[1].fY));
+ }
+
+ SkRect r;
+ r.set(pts, 2);
+
+ erase(fMinSurface);
+ this->setupPaint(&paint);
+ paint.setColor(FAT_PIXEL_COLOR);
+ fMinSurface->getCanvas()->drawRect(r, paint);
+ this->copyMinToMax();
+
+ SkCanvas* max = fMaxSurface->getCanvas();
+
+ fMatrix.mapPoints(pts, 2);
+ r.set(pts, 2);
+ this->drawRectSkeleton(max, r);
+
+ fMaxSurface->draw(canvas, 0, 0, NULL);
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class IndexClick : public SkView::Click {
+ int fIndex;
+public:
+ IndexClick(SkView* v, int index) : SkView::Click(v), fIndex(index) {}
+
+ static int GetIndex(SkView::Click* click) {
+ return ((IndexClick*)click)->fIndex;
+ }
+};
+
+class DrawLineView : public SampleView {
+ FatBits fFB;
+ SkPoint fPts[2];
+ bool fIsRect;
+public:
+ DrawLineView() {
+ fFB.setWHZ(24, 16, 48);
+ fPts[0].set(48, 48);
+ fPts[1].set(48 * 5, 48 * 4);
+ fIsRect = false;
+ }
+
+ void setStyle(FatBits::Style s) {
+ fFB.setStyle(s);
+ this->inval(NULL);
+ }
+
+protected:
+ virtual bool onQuery(SkEvent* evt) SK_OVERRIDE {
+ if (SampleCode::TitleQ(*evt)) {
+ SampleCode::TitleR(evt, "FatBits");
+ return true;
+ }
+ SkUnichar uni;
+ if (SampleCode::CharQ(*evt, &uni)) {
+ switch (uni) {
+ case 'c':
+ fFB.setUseClip(!fFB.getUseClip());
+ this->inval(NULL);
+ return true;
+ case 'r':
+ fIsRect = !fIsRect;
+ this->inval(NULL);
+ return true;
+ case 'x':
+ fFB.setGrid(!fFB.getGrid());
+ this->inval(NULL);
+ return true;
+ case 's':
+ if (FatBits::kStroke_Style == fFB.getStyle()) {
+ this->setStyle(FatBits::kHair_Style);
+ } else {
+ this->setStyle(FatBits::kStroke_Style);
+ }
+ return true;
+ case 'a':
+ fFB.setAA(!fFB.getAA());
+ this->inval(NULL);
+ return true;
+ case 'w':
+ fFB.setShowSkeleton(!fFB.getShowSkeleton());
+ this->inval(NULL);
+ return true;
+ case 'g':
+ fFB.setUseGPU(!fFB.getUseGPU());
+ this->inval(NULL);
+ return true;
+ }
+ }
+ return this->INHERITED::onQuery(evt);
+ }
+
+ virtual void onDrawContent(SkCanvas* canvas) {
+ fFB.drawBG(canvas);
+ if (fIsRect) {
+ fFB.drawRect(canvas, fPts);
+ } else {
+ fFB.drawLine(canvas, fPts);
+ }
+ fFB.drawFG(canvas);
+
+ {
+ SkString str;
+ str.printf("%s %s %s %s",
+ fFB.getAA() ? "AA" : "BW",
+ FatBits::kHair_Style == fFB.getStyle() ? "Hair" : "Stroke",
+ fFB.getUseGPU() ? "GPU" : "CPU",
+ fFB.getUseClip() ? "clip" : "noclip");
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setTextSize(16);
+ paint.setColor(SK_ColorBLUE);
+ canvas->drawText(str.c_str(), str.size(), 10, 16, paint);
+ }
+ }
+
+ virtual SkView::Click* onFindClickHandler(SkScalar x, SkScalar y) {
+ SkPoint pt = { x, y };
+ int index = -1;
+ SkScalar tol = 12;
+ if (fPts[0].equalsWithinTolerance(pt, tol)) {
+ index = 0;
+ } else if (fPts[1].equalsWithinTolerance(pt, tol)) {
+ index = 1;
+ }
+ return new IndexClick(this, index);
+ }
+
+ virtual bool onClick(Click* click) {
+ int index = IndexClick::GetIndex(click);
+ if (index >= 0 && index <= 1) {
+ fPts[index] = click->fCurr;
+ } else {
+ SkScalar dx = click->fCurr.fX - click->fPrev.fX;
+ SkScalar dy = click->fCurr.fY - click->fPrev.fY;
+ fPts[0].offset(dx, dy);
+ fPts[1].offset(dx, dy);
+ }
+ this->inval(NULL);
+ return true;
+ }
+
+private:
+
+ typedef SampleView INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static SkView* MyFactory() { return new DrawLineView; }
+static SkViewRegister reg(MyFactory);
+
diff --git a/samplecode/SampleHairline.cpp b/samplecode/SampleHairline.cpp
new file mode 100644
index 0000000000..28ed68f23d
--- /dev/null
+++ b/samplecode/SampleHairline.cpp
@@ -0,0 +1,279 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SampleCode.h"
+#include "SkView.h"
+#include "SkCanvas.h"
+#include "Sk64.h"
+#include "SkCornerPathEffect.h"
+#include "SkGradientShader.h"
+#include "SkGraphics.h"
+#include "SkImageDecoder.h"
+#include "SkKernel33MaskFilter.h"
+#include "SkPath.h"
+#include "SkRandom.h"
+#include "SkRegion.h"
+#include "SkShader.h"
+#include "SkUtils.h"
+#include "SkColorPriv.h"
+#include "SkColorFilter.h"
+#include "SkTime.h"
+#include "SkTypeface.h"
+#include "SkXfermode.h"
+
+#include "SkStream.h"
+#include "SkXMLParser.h"
+#include "SkColorPriv.h"
+#include "SkImageDecoder.h"
+
+static SkRandom gRand;
+
+static void test_chromium_9005() {
+ SkBitmap bm;
+ bm.setConfig(SkBitmap::kARGB_8888_Config, 800, 600);
+ bm.allocPixels();
+
+ SkCanvas canvas(bm);
+
+ SkPoint pt0 = { SkFloatToScalar(799.33374f), SkFloatToScalar(1.2360189f) };
+ SkPoint pt1 = { SkFloatToScalar(808.49969f), SkFloatToScalar(-7.4338055f) };
+
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ canvas.drawLine(pt0.fX, pt0.fY, pt1.fX, pt1.fY, paint);
+}
+
+static void generate_pts(SkPoint pts[], int count, int w, int h) {
+ for (int i = 0; i < count; i++) {
+ pts[i].set(gRand.nextUScalar1() * 3 * w - SkIntToScalar(w),
+ gRand.nextUScalar1() * 3 * h - SkIntToScalar(h));
+ }
+}
+
+static bool check_zeros(const SkPMColor pixels[], int count, int skip) {
+ for (int i = 0; i < count; i++) {
+ if (*pixels) {
+ return false;
+ }
+ pixels += skip;
+ }
+ return true;
+}
+
+static bool check_bitmap_margin(const SkBitmap& bm, int margin) {
+ size_t rb = bm.rowBytes();
+ for (int i = 0; i < margin; i++) {
+ if (!check_zeros(bm.getAddr32(0, i), bm.width(), 1)) {
+ return false;
+ }
+ int bottom = bm.height() - i - 1;
+ if (!check_zeros(bm.getAddr32(0, bottom), bm.width(), 1)) {
+ return false;
+ }
+ // left column
+ if (!check_zeros(bm.getAddr32(i, 0), bm.height(), rb >> 2)) {
+ return false;
+ }
+ int right = bm.width() - margin + i;
+ if (!check_zeros(bm.getAddr32(right, 0), bm.height(), rb >> 2)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+#define WIDTH 620
+#define HEIGHT 460
+#define MARGIN 10
+
+static void line_proc(SkCanvas* canvas, const SkPaint& paint,
+ const SkBitmap& bm) {
+ const int N = 2;
+ SkPoint pts[N];
+ for (int i = 0; i < 400; i++) {
+ generate_pts(pts, N, WIDTH, HEIGHT);
+
+ canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint);
+ if (!check_bitmap_margin(bm, MARGIN)) {
+ SkDebugf("---- hairline failure (%g %g) (%g %g)\n",
+ pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
+ break;
+ }
+ }
+}
+
+static void poly_proc(SkCanvas* canvas, const SkPaint& paint,
+ const SkBitmap& bm) {
+ const int N = 8;
+ SkPoint pts[N];
+ for (int i = 0; i < 50; i++) {
+ generate_pts(pts, N, WIDTH, HEIGHT);
+
+ SkPath path;
+ path.moveTo(pts[0]);
+ for (int j = 1; j < N; j++) {
+ path.lineTo(pts[j]);
+ }
+ canvas->drawPath(path, paint);
+ }
+}
+
+static SkPoint ave(const SkPoint& a, const SkPoint& b) {
+ SkPoint c = a + b;
+ c.fX = SkScalarHalf(c.fX);
+ c.fY = SkScalarHalf(c.fY);
+ return c;
+}
+
+static void quad_proc(SkCanvas* canvas, const SkPaint& paint,
+ const SkBitmap& bm) {
+ const int N = 30;
+ SkPoint pts[N];
+ for (int i = 0; i < 10; i++) {
+ generate_pts(pts, N, WIDTH, HEIGHT);
+
+ SkPath path;
+ path.moveTo(pts[0]);
+ for (int j = 1; j < N - 2; j++) {
+ path.quadTo(pts[j], ave(pts[j], pts[j+1]));
+ }
+ path.quadTo(pts[N - 2], pts[N - 1]);
+
+ canvas->drawPath(path, paint);
+ }
+}
+
+static void add_cubic(SkPath* path, const SkPoint& mid, const SkPoint& end) {
+ SkPoint start;
+ path->getLastPt(&start);
+ path->cubicTo(ave(start, mid), ave(mid, end), end);
+}
+
+static void cube_proc(SkCanvas* canvas, const SkPaint& paint,
+ const SkBitmap& bm) {
+ const int N = 30;
+ SkPoint pts[N];
+ for (int i = 0; i < 10; i++) {
+ generate_pts(pts, N, WIDTH, HEIGHT);
+
+ SkPath path;
+ path.moveTo(pts[0]);
+ for (int j = 1; j < N - 2; j++) {
+ add_cubic(&path, pts[j], ave(pts[j], pts[j+1]));
+ }
+ add_cubic(&path, pts[N - 2], pts[N - 1]);
+
+ canvas->drawPath(path, paint);
+ }
+}
+
+typedef void (*HairProc)(SkCanvas*, const SkPaint&, const SkBitmap&);
+
+static const struct {
+ const char* fName;
+ HairProc fProc;
+} gProcs[] = {
+ { "line", line_proc },
+ { "poly", poly_proc },
+ { "quad", quad_proc },
+ { "cube", cube_proc },
+};
+
+static int cycle_hairproc_index(int index) {
+ return (index + 1) % SK_ARRAY_COUNT(gProcs);
+}
+
+class HairlineView : public SampleView {
+ SkMSec fNow;
+ int fProcIndex;
+ bool fDoAA;
+public:
+ HairlineView() {
+ fCounter = 0;
+ fProcIndex = 0;
+ fDoAA = true;
+ fNow = 0;
+ }
+
+protected:
+ // overrides from SkEventSink
+ virtual bool onQuery(SkEvent* evt) {
+ if (SampleCode::TitleQ(*evt)) {
+ SkString str;
+ str.printf("Hair-%s", gProcs[fProcIndex].fName);
+ SampleCode::TitleR(evt, str.c_str());
+ return true;
+ }
+ return this->INHERITED::onQuery(evt);
+ }
+
+ void show_bitmaps(SkCanvas* canvas, const SkBitmap& b0, const SkBitmap& b1,
+ const SkIRect& inset) {
+ canvas->drawBitmap(b0, 0, 0, NULL);
+ canvas->drawBitmap(b1, SkIntToScalar(b0.width()), 0, NULL);
+ }
+
+ int fCounter;
+
+ virtual void onDrawContent(SkCanvas* canvas) {
+ gRand.setSeed(fNow);
+
+ if (false) { // avoid bit rot, suppress warning
+ test_chromium_9005();
+ }
+
+ SkBitmap bm, bm2;
+ bm.setConfig(SkBitmap::kARGB_8888_Config,
+ WIDTH + MARGIN*2,
+ HEIGHT + MARGIN*2);
+ bm.allocPixels();
+ // this will erase our margin, which we want to always stay 0
+ bm.eraseColor(SK_ColorTRANSPARENT);
+
+ bm2.setConfig(SkBitmap::kARGB_8888_Config, WIDTH, HEIGHT,
+ bm.rowBytes());
+ bm2.setPixels(bm.getAddr32(MARGIN, MARGIN));
+
+ SkCanvas c2(bm2);
+ SkPaint paint;
+ paint.setAntiAlias(fDoAA);
+ paint.setStyle(SkPaint::kStroke_Style);
+
+ bm2.eraseColor(SK_ColorTRANSPARENT);
+ gProcs[fProcIndex].fProc(&c2, paint, bm);
+ canvas->drawBitmap(bm2, SkIntToScalar(10), SkIntToScalar(10), NULL);
+
+ SkMSec now = SampleCode::GetAnimTime();
+ if (fNow != now) {
+ fNow = now;
+ fCounter += 1;
+ fDoAA = !fDoAA;
+ if (fCounter > 50) {
+ fProcIndex = cycle_hairproc_index(fProcIndex);
+ // todo: signal that we want to rebuild our TITLE
+ fCounter = 0;
+ }
+ this->inval(NULL);
+ }
+ }
+
+ virtual SkView::Click* onFindClickHandler(SkScalar x, SkScalar y) {
+ fDoAA = !fDoAA;
+ this->inval(NULL);
+ return this->INHERITED::onFindClickHandler(x, y);
+ }
+
+
+private:
+ typedef SampleView INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static SkView* MyFactory() { return new HairlineView; }
+static SkViewRegister reg(MyFactory);
+
diff --git a/samplecode/SampleLayerMask.cpp b/samplecode/SampleLayerMask.cpp
new file mode 100644
index 0000000000..d65c131897
--- /dev/null
+++ b/samplecode/SampleLayerMask.cpp
@@ -0,0 +1,75 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SampleCode.h"
+#include "SkCanvas.h"
+#include "SkPaint.h"
+#include "SkView.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+class LayerMaskView : public SampleView {
+public:
+ LayerMaskView() {
+ this->setBGColor(0xFFDDDDDD);
+ }
+
+protected:
+ // overrides from SkEventSink
+ virtual bool onQuery(SkEvent* evt) {
+ if (SampleCode::TitleQ(*evt)) {
+ SampleCode::TitleR(evt, "LayerMask");
+ return true;
+ }
+ return this->INHERITED::onQuery(evt);
+ }
+
+ void drawMask(SkCanvas* canvas, const SkRect& r) {
+ SkPaint paint;
+ paint.setAntiAlias(true);
+
+ if (true) {
+ SkBitmap mask;
+ int w = SkScalarRound(r.width());
+ int h = SkScalarRound(r.height());
+ mask.setConfig(SkBitmap::kARGB_8888_Config, w, h);
+ mask.allocPixels();
+ mask.eraseColor(SK_ColorTRANSPARENT);
+ SkCanvas c(mask);
+ SkRect bounds = r;
+ bounds.offset(-bounds.fLeft, -bounds.fTop);
+ c.drawOval(bounds, paint);
+
+ paint.setXfermodeMode(SkXfermode::kDstIn_Mode);
+ canvas->drawBitmap(mask, r.fLeft, r.fTop, &paint);
+ } else {
+ SkPath p;
+ p.addOval(r);
+ p.setFillType(SkPath::kInverseWinding_FillType);
+ paint.setXfermodeMode(SkXfermode::kDstOut_Mode);
+ canvas->drawPath(p, paint);
+ }
+ }
+
+ virtual void onDrawContent(SkCanvas* canvas) {
+ SkRect r;
+ r.set(SkIntToScalar(20), SkIntToScalar(20), SkIntToScalar(120), SkIntToScalar(120));
+ canvas->saveLayer(&r, NULL, SkCanvas::kARGB_ClipLayer_SaveFlag);
+ canvas->drawColor(SK_ColorRED);
+ drawMask(canvas, r);
+ canvas->restore();
+ }
+
+private:
+ typedef SampleView INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+static SkView* MyFactory() { return new LayerMaskView; }
+static SkViewRegister reg(MyFactory);
+
diff --git a/samplecode/SampleLayers.cpp b/samplecode/SampleLayers.cpp
new file mode 100644
index 0000000000..cf6b0090f4
--- /dev/null
+++ b/samplecode/SampleLayers.cpp
@@ -0,0 +1,275 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SampleCode.h"
+#include "SkView.h"
+#include "SkCanvas.h"
+#include "SkBlurMaskFilter.h"
+#include "SkCamera.h"
+#include "SkColorFilter.h"
+#include "SkColorPriv.h"
+#include "SkDevice.h"
+#include "SkGradientShader.h"
+#include "SkImageDecoder.h"
+#include "SkInterpolator.h"
+#include "SkMaskFilter.h"
+#include "SkPath.h"
+#include "SkRegion.h"
+#include "SkShader.h"
+#include "SkTime.h"
+#include "SkTypeface.h"
+#include "SkUtils.h"
+#include "SkKey.h"
+#include "SkXfermode.h"
+#include "SkDrawFilter.h"
+
+static void make_paint(SkPaint* paint) {
+ SkColor colors[] = { 0, SK_ColorWHITE };
+ SkPoint pts[] = { { 0, 0 }, { 0, SK_Scalar1*20 } };
+ SkShader* s = SkGradientShader::CreateLinear(pts, colors, NULL, 2, SkShader::kClamp_TileMode);
+
+ paint->setShader(s)->unref();
+ paint->setXfermodeMode(SkXfermode::kDstIn_Mode);
+}
+
+static void dump_layers(const char label[], SkCanvas* canvas) {
+ SkDebugf("Dump Layers(%s)\n", label);
+
+ SkCanvas::LayerIter iter(canvas, true);
+ int index = 0;
+ while (!iter.done()) {
+ const SkBitmap& bm = iter.device()->accessBitmap(false);
+ const SkIRect& clip = iter.clip().getBounds();
+ SkDebugf("Layer[%d] bitmap [%d %d] X=%d Y=%d clip=[%d %d %d %d] alpha=%d\n", index++,
+ bm.width(), bm.height(), iter.x(), iter.y(),
+ clip.fLeft, clip.fTop, clip.fRight, clip.fBottom,
+ iter.paint().getAlpha());
+ iter.next();
+ }
+}
+
+// test drawing with strips of fading gradient above and below
+static void test_fade(SkCanvas* canvas) {
+ SkAutoCanvasRestore ar(canvas, true);
+
+ SkRect r;
+
+ SkPaint p;
+ p.setAlpha(0x88);
+
+ SkAutoCanvasRestore(canvas, false);
+
+ // create the layers
+
+ r.set(0, 0, SkIntToScalar(100), SkIntToScalar(100));
+ canvas->clipRect(r);
+
+ r.fBottom = SkIntToScalar(20);
+ canvas->saveLayer(&r, NULL, (SkCanvas::SaveFlags)(SkCanvas::kHasAlphaLayer_SaveFlag | SkCanvas::kFullColorLayer_SaveFlag));
+
+ r.fTop = SkIntToScalar(80);
+ r.fBottom = SkIntToScalar(100);
+ canvas->saveLayer(&r, NULL, (SkCanvas::SaveFlags)(SkCanvas::kHasAlphaLayer_SaveFlag | SkCanvas::kFullColorLayer_SaveFlag));
+
+ // now draw the "content"
+
+ if (true) {
+ r.set(0, 0, SkIntToScalar(100), SkIntToScalar(100));
+
+ canvas->saveLayerAlpha(&r, 0x80);
+
+ SkPaint p;
+ p.setColor(SK_ColorRED);
+ p.setAntiAlias(true);
+ canvas->drawOval(r, p);
+
+ dump_layers("inside layer alpha", canvas);
+
+ canvas->restore();
+ } else {
+ r.set(0, 0, SkIntToScalar(100), SkIntToScalar(100));
+
+ SkPaint p;
+ p.setColor(SK_ColorRED);
+ p.setAntiAlias(true);
+ canvas->drawOval(r, p);
+ }
+
+// return;
+
+ dump_layers("outside layer alpha", canvas);
+
+ // now apply an effect
+
+ SkPaint paint;
+ make_paint(&paint);
+ r.set(0, 0, SkIntToScalar(100), SkIntToScalar(20));
+// SkDebugf("--------- draw top grad\n");
+ canvas->drawRect(r, paint);
+
+ SkMatrix m;
+ SkShader* s = paint.getShader();
+ m.setScale(SK_Scalar1, -SK_Scalar1);
+ m.postTranslate(0, SkIntToScalar(100));
+ s->setLocalMatrix(m);
+
+ r.fTop = SkIntToScalar(80);
+ r.fBottom = SkIntToScalar(100);
+// SkDebugf("--------- draw bot grad\n");
+ canvas->drawRect(r, paint);
+}
+
+class RedFilter : public SkDrawFilter {
+public:
+ virtual bool filter(SkPaint* p, SkDrawFilter::Type) SK_OVERRIDE {
+ fColor = p->getColor();
+ if (fColor == SK_ColorRED) {
+ p->setColor(SK_ColorGREEN);
+ }
+ return true;
+ }
+
+private:
+ SkColor fColor;
+};
+
+class LayersView : public SkView {
+public:
+ LayersView() {}
+
+protected:
+ // overrides from SkEventSink
+ virtual bool onQuery(SkEvent* evt) {
+ if (SampleCode::TitleQ(*evt)) {
+ SampleCode::TitleR(evt, "Layers");
+ return true;
+ }
+ return this->INHERITED::onQuery(evt);
+ }
+
+ void drawBG(SkCanvas* canvas) {
+ canvas->drawColor(SK_ColorGRAY);
+ }
+
+ virtual void onDraw(SkCanvas* canvas) {
+ this->drawBG(canvas);
+
+ if (true) {
+ SkRect r;
+ r.set(SkIntToScalar(0), SkIntToScalar(0),
+ SkIntToScalar(220), SkIntToScalar(120));
+ SkPaint p;
+ canvas->saveLayer(&r, &p);
+ canvas->drawColor(0xFFFF0000);
+ p.setAlpha(0); // or 0
+ p.setXfermodeMode(SkXfermode::kSrc_Mode);
+ canvas->drawOval(r, p);
+ canvas->restore();
+ return;
+ }
+
+ if (false) {
+ SkRect r;
+ r.set(SkIntToScalar(0), SkIntToScalar(0),
+ SkIntToScalar(220), SkIntToScalar(120));
+ SkPaint p;
+ p.setAlpha(0x88);
+ p.setAntiAlias(true);
+
+ if (true) {
+ canvas->saveLayer(&r, &p);
+ p.setColor(0xFFFF0000);
+ canvas->drawOval(r, p);
+ canvas->restore();
+ }
+
+ p.setColor(0xFF0000FF);
+ r.offset(SkIntToScalar(20), SkIntToScalar(50));
+ canvas->drawOval(r, p);
+ }
+
+ if (false) {
+ SkPaint p;
+ p.setAlpha(0x88);
+ p.setAntiAlias(true);
+
+ canvas->translate(SkIntToScalar(300), 0);
+
+ SkRect r;
+ r.set(SkIntToScalar(0), SkIntToScalar(0),
+ SkIntToScalar(220), SkIntToScalar(60));
+
+ canvas->saveLayer(&r, &p, (SkCanvas::SaveFlags)(SkCanvas::kHasAlphaLayer_SaveFlag | SkCanvas::kFullColorLayer_SaveFlag));
+// canvas->clipRect(r, SkRegion::kDifference_Op);
+// canvas->clipRect(r, SkRegion::kIntersect_Op);
+
+ r.set(SkIntToScalar(0), SkIntToScalar(0),
+ SkIntToScalar(220), SkIntToScalar(120));
+ p.setColor(SK_ColorBLUE);
+ canvas->drawOval(r, p);
+ canvas->restore();
+ return;
+ }
+
+ //canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
+ test_fade(canvas);
+ return;
+
+ // canvas->setDrawFilter(new RedFilter)->unref();
+
+ SkRect r;
+ SkPaint p;
+
+ canvas->translate(SkIntToScalar(220), SkIntToScalar(20));
+
+ p.setAntiAlias(true);
+ r.set(SkIntToScalar(20), SkIntToScalar(20),
+ SkIntToScalar(220), SkIntToScalar(120));
+
+ p.setColor(SK_ColorBLUE);
+ // p.setMaskFilter(SkBlurMaskFilter::Create(SkIntToScalar(8), SkBlurMaskFilter::kNormal_BlurStyle))->unref();
+ canvas->drawRect(r, p);
+ p.setMaskFilter(NULL);
+
+ SkRect bounds = r;
+ bounds.fBottom = bounds.centerY();
+ canvas->saveLayer(&bounds, NULL, SkCanvas::kARGB_NoClipLayer_SaveFlag);
+
+ p.setColor(SK_ColorRED);
+ canvas->drawOval(r, p);
+
+ p.setAlpha(0x80);
+ p.setXfermodeMode(SkXfermode::kDstIn_Mode);
+ canvas->drawRect(bounds, p);
+
+ canvas->restore();
+ }
+
+ virtual SkView::Click* onFindClickHandler(SkScalar x, SkScalar y) {
+ this->inval(NULL);
+
+ return this->INHERITED::onFindClickHandler(x, y);
+ }
+
+ virtual bool onClick(Click* click) {
+ return this->INHERITED::onClick(click);
+ }
+
+ virtual bool handleKey(SkKey key) {
+ this->inval(NULL);
+ return true;
+ }
+
+private:
+ typedef SkView INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static SkView* MyFactory() { return new LayersView; }
+static SkViewRegister reg(MyFactory);
+
diff --git a/samplecode/SampleMipMap.cpp b/samplecode/SampleMipMap.cpp
new file mode 100644
index 0000000000..f3a12cd5ae
--- /dev/null
+++ b/samplecode/SampleMipMap.cpp
@@ -0,0 +1,161 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SampleCode.h"
+#include "SkView.h"
+#include "SkCanvas.h"
+#include "SkDevice.h"
+#include "SkPaint.h"
+#include "SkShader.h"
+
+static SkBitmap createBitmap(int n) {
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config, n, n);
+ bitmap.allocPixels();
+ bitmap.eraseColor(SK_ColorTRANSPARENT);
+
+ SkCanvas canvas(bitmap);
+ SkRect r;
+ r.set(0, 0, SkIntToScalar(n), SkIntToScalar(n));
+ SkPaint paint;
+ paint.setAntiAlias(true);
+
+ paint.setColor(SK_ColorRED);
+ canvas.drawOval(r, paint);
+ paint.setColor(SK_ColorBLUE);
+ paint.setStrokeWidth(SkIntToScalar(n)/15);
+ paint.setStyle(SkPaint::kStroke_Style);
+ canvas.drawLine(0, 0, r.fRight, r.fBottom, paint);
+ canvas.drawLine(0, r.fBottom, r.fRight, 0, paint);
+
+ return bitmap;
+}
+
+class MipMapView : public SampleView {
+ SkBitmap fBitmap;
+ enum {
+ N = 64
+ };
+ bool fOnce;
+public:
+ MipMapView() {
+ fOnce = false;
+ }
+
+ void init() {
+ if (fOnce) {
+ return;
+ }
+ fOnce = true;
+
+ fBitmap = createBitmap(N);
+
+ fWidth = N;
+ }
+
+protected:
+ // overrides from SkEventSink
+ virtual bool onQuery(SkEvent* evt) {
+ if (SampleCode::TitleQ(*evt)) {
+ SampleCode::TitleR(evt, "MipMaps");
+ return true;
+ }
+ return this->INHERITED::onQuery(evt);
+ }
+
+ void drawN(SkCanvas* canvas, const SkBitmap& bitmap) {
+ SkAutoCanvasRestore acr(canvas, true);
+ for (int i = N; i > 1; i >>= 1) {
+ canvas->drawBitmap(bitmap, 0, 0, NULL);
+ canvas->translate(SkIntToScalar(N + 8), 0);
+ canvas->scale(SK_ScalarHalf, SK_ScalarHalf);
+ }
+ }
+
+ void drawN2(SkCanvas* canvas, const SkBitmap& bitmap) {
+ SkBitmap bg;
+ bg.setConfig(SkBitmap::kARGB_8888_Config, N, N);
+ bg.allocPixels();
+
+ SkAutoCanvasRestore acr(canvas, true);
+ for (int i = 0; i < 6; i++) {
+ bg.eraseColor(SK_ColorTRANSPARENT);
+ SkCanvas c(bg);
+ c.scale(SK_Scalar1 / (1 << i), SK_Scalar1 / (1 << i));
+ c.drawBitmap(bitmap, 0, 0, NULL);
+
+ canvas->save();
+ canvas->scale(SkIntToScalar(1 << i), SkIntToScalar(1 << i));
+ canvas->drawBitmap(bg, 0, 0, NULL);
+ canvas->restore();
+ canvas->translate(SkIntToScalar(N + 8), 0);
+ }
+ }
+
+ virtual void onDrawContent(SkCanvas* canvas) {
+ this->init();
+ canvas->translate(SkIntToScalar(10), SkIntToScalar(10));
+
+ canvas->scale(1.00000001f, 0.9999999f);
+
+ drawN2(canvas, fBitmap);
+
+ canvas->translate(0, SkIntToScalar(N + 8));
+ SkBitmap bitmap(fBitmap);
+ bitmap.buildMipMap();
+ drawN2(canvas, bitmap);
+
+ SkScalar time = SampleCode::GetAnimScalar(SkIntToScalar(1)/4,
+ SkIntToScalar(2));
+ if (time >= SK_Scalar1) {
+ time = SkIntToScalar(2) - time;
+ }
+ fWidth = 8 + SkScalarRound(N * time);
+
+ SkRect dst;
+ dst.set(0, 0, SkIntToScalar(fWidth), SkIntToScalar(fWidth));
+
+ SkPaint paint;
+ paint.setFilterBitmap(true);
+ paint.setAntiAlias(true);
+
+ canvas->translate(0, SkIntToScalar(N + 8));
+ canvas->drawBitmapRect(fBitmap, NULL, dst, NULL);
+ canvas->translate(SkIntToScalar(N + 8), 0);
+ canvas->drawBitmapRect(fBitmap, NULL, dst, &paint);
+ canvas->translate(-SkIntToScalar(N + 8), SkIntToScalar(N + 8));
+ canvas->drawBitmapRect(bitmap, NULL, dst, NULL);
+ canvas->translate(SkIntToScalar(N + 8), 0);
+ canvas->drawBitmapRect(bitmap, NULL, dst, &paint);
+
+ SkShader* s = SkShader::CreateBitmapShader(bitmap,
+ SkShader::kRepeat_TileMode,
+ SkShader::kRepeat_TileMode);
+ paint.setShader(s)->unref();
+ SkMatrix m;
+ m.setScale(SkIntToScalar(fWidth) / N,
+ SkIntToScalar(fWidth) / N);
+ s->setLocalMatrix(m);
+ SkRect r;
+ r.set(0, 0, SkIntToScalar(4*N), SkIntToScalar(5*N/2));
+ r.offset(SkIntToScalar(N + 12), -SkIntToScalar(N + 4));
+ canvas->drawRect(r, paint);
+
+ this->inval(NULL);
+ }
+
+private:
+ int fWidth;
+
+ typedef SampleView INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static SkView* MyFactory() { return new MipMapView; }
+static SkViewRegister reg(MyFactory);
+
diff --git a/samplecode/SampleOvalTest.cpp b/samplecode/SampleOvalTest.cpp
new file mode 100644
index 0000000000..88b7d5fa0b
--- /dev/null
+++ b/samplecode/SampleOvalTest.cpp
@@ -0,0 +1,117 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SampleCode.h"
+#include "SkView.h"
+#include "SkCanvas.h"
+
+static const int kILimit = 101;
+static const SkScalar kLimit = SK_Scalar1 * kILimit;
+
+class OvalTestView : public SampleView {
+public:
+ SkSize fSize;
+ SkPMColor fInsideColor; // signals an interior pixel that was not set
+ SkPMColor fOutsideColor; // signals an exterior pixels that was set
+ SkBitmap fBitmap;
+
+ OvalTestView() {
+ fSize.set(SK_Scalar1, SK_Scalar1);
+
+ fBitmap.setConfig(SkBitmap::kARGB_8888_Config, kILimit, kILimit);
+ fBitmap.allocPixels();
+
+ fInsideColor = SkPreMultiplyColor(SK_ColorRED);
+ fOutsideColor = SkPreMultiplyColor(SK_ColorGREEN);
+
+ this->setBGColor(0xFFDDDDDD);
+ }
+
+protected:
+ // overrides from SkEventSink
+ virtual bool onQuery(SkEvent* evt) {
+ if (SampleCode::TitleQ(*evt)) {
+ SampleCode::TitleR(evt, "OvalTest");
+ return true;
+ }
+ return this->INHERITED::onQuery(evt);
+ }
+
+ void drawOval() {
+ SkCanvas canvas(fBitmap);
+ SkPaint p;
+
+ fBitmap.eraseColor(SK_ColorTRANSPARENT);
+ canvas.drawOval(SkRect::MakeSize(fSize), p);
+ }
+
+ int checkOval(int* flatCount, int* buldgeCount) {
+ int flatc = 0;
+ int buldgec = 0;
+ const SkScalar rad = SkScalarHalf(fSize.width());
+ SkScalar cx = SkScalarHalf(fSize.width());
+ SkScalar cy = SkScalarHalf(fSize.height());
+ for (int y = 0; y < kILimit; y++) {
+ for (int x = 0; x < kILimit; x++) {
+ // measure from pixel centers
+ SkScalar px = SkIntToScalar(x) + SK_ScalarHalf;
+ SkScalar py = SkIntToScalar(y) + SK_ScalarHalf;
+
+ SkPMColor* ptr = fBitmap.getAddr32(x, y);
+ SkScalar dist = SkPoint::Length(px - cx, py - cy);
+ if (dist <= rad && !*ptr) {
+ flatc++;
+ *ptr = fInsideColor;
+ } else if (dist > rad && *ptr) {
+ buldgec++;
+ *ptr = fOutsideColor;
+ }
+ }
+ }
+ if (flatCount) *flatCount = flatc;
+ if (buldgeCount) *buldgeCount = buldgec;
+ return flatc + buldgec;
+ }
+
+ virtual void onDrawContent(SkCanvas* canvas) {
+ this->drawOval();
+ int flatCount, buldgeCount;
+ this->checkOval(&flatCount, &buldgeCount);
+ this->inval(NULL);
+
+ canvas->drawBitmap(fBitmap, SkIntToScalar(20), SkIntToScalar(20), NULL);
+
+
+ static int gFlatCount;
+ static int gBuldgeCount;
+ gFlatCount += flatCount;
+ gBuldgeCount += buldgeCount;
+
+ if (fSize.fWidth < kLimit) {
+ SkDebugf("--- width=%g, flat=%d buldge=%d total: flat=%d buldge=%d\n", fSize.fWidth,
+ flatCount, buldgeCount, gFlatCount, gBuldgeCount);
+ fSize.fWidth += SK_Scalar1;
+ fSize.fHeight += SK_Scalar1;
+ } else {
+ // fSize.set(SK_Scalar1, SK_Scalar1);
+ }
+ }
+
+ virtual SkView::Click* onFindClickHandler(SkScalar x, SkScalar y) {
+ this->inval(NULL);
+ return NULL;
+ }
+
+private:
+ typedef SampleView INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+static SkView* MyFactory() { return new OvalTestView; }
+static SkViewRegister reg(MyFactory);
+
diff --git a/samplecode/SamplePictFile.cpp b/samplecode/SamplePictFile.cpp
new file mode 100644
index 0000000000..b53ba83600
--- /dev/null
+++ b/samplecode/SamplePictFile.cpp
@@ -0,0 +1,142 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SampleCode.h"
+#include "SkDumpCanvas.h"
+#include "SkView.h"
+#include "SkCanvas.h"
+#include "Sk64.h"
+#include "SkGradientShader.h"
+#include "SkGraphics.h"
+#include "SkImageDecoder.h"
+#include "SkOSFile.h"
+#include "SkPath.h"
+#include "SkPicture.h"
+#include "SkRandom.h"
+#include "SkRegion.h"
+#include "SkShader.h"
+#include "SkUtils.h"
+#include "SkColorPriv.h"
+#include "SkColorFilter.h"
+#include "SkTime.h"
+#include "SkTypeface.h"
+#include "SkXfermode.h"
+
+#include "SkStream.h"
+#include "SkXMLParser.h"
+
+class PictFileView : public SampleView {
+ SkString fFilename;
+ SkPicture* fPicture;
+ SkPicture* fBBoxPicture;
+ bool fUseBBox;
+
+ static SkPicture* LoadPicture(const char path[], bool useBBox) {
+ SkPicture* pic = NULL;
+
+ SkBitmap bm;
+ if (SkImageDecoder::DecodeFile(path, &bm)) {
+ bm.setImmutable();
+ pic = SkNEW(SkPicture);
+ SkCanvas* can = pic->beginRecording(bm.width(), bm.height());
+ can->drawBitmap(bm, 0, 0, NULL);
+ pic->endRecording();
+ } else {
+ SkFILEStream stream(path);
+ if (stream.isValid()) {
+ pic = SkNEW_ARGS(SkPicture,
+ (&stream, NULL, &SkImageDecoder::DecodeStream));
+ }
+
+ if (false) { // re-record
+ SkPicture p2;
+ pic->draw(p2.beginRecording(pic->width(), pic->height()));
+ p2.endRecording();
+
+ SkString path2(path);
+ path2.append(".new.skp");
+ SkFILEWStream writer(path2.c_str());
+ p2.serialize(&writer);
+ }
+ }
+
+ if (useBBox) {
+ SkPicture* bboxPicture = SkNEW(SkPicture);
+ pic->draw(bboxPicture->beginRecording(pic->width(), pic->height(),
+ SkPicture::kOptimizeForClippedPlayback_RecordingFlag));
+ bboxPicture->endRecording();
+ SkDELETE(pic);
+ return bboxPicture;
+
+ } else {
+ return pic;
+ }
+ }
+
+public:
+ PictFileView(const char name[] = NULL) : fFilename(name) {
+ fPicture = NULL;
+ fBBoxPicture = NULL;
+ fUseBBox = false;
+ }
+
+ virtual ~PictFileView() {
+ SkSafeUnref(fPicture);
+ SkSafeUnref(fBBoxPicture);
+ }
+
+protected:
+ // overrides from SkEventSink
+ virtual bool onQuery(SkEvent* evt) {
+ if (SampleCode::TitleQ(*evt)) {
+ SkString name("P:");
+ const char* basename = strrchr(fFilename.c_str(), SkPATH_SEPARATOR);
+ name.append(basename ? basename+1: fFilename.c_str());
+ if (fUseBBox) {
+ name.append(" <bbox>");
+ }
+ SampleCode::TitleR(evt, name.c_str());
+ return true;
+ }
+ return this->INHERITED::onQuery(evt);
+ }
+
+ virtual bool onEvent(const SkEvent& evt) {
+ if (evt.isType("PictFileView::toggleBBox")) {
+ fUseBBox = !fUseBBox;
+ return true;
+ }
+ return this->INHERITED::onEvent(evt);
+ }
+
+ virtual void onDrawContent(SkCanvas* canvas) {
+ SkPicture** picture = fUseBBox ? &fBBoxPicture : &fPicture;
+
+ if (!*picture) {
+ *picture = LoadPicture(fFilename.c_str(), fUseBBox);
+ }
+ if (*picture) {
+ canvas->drawPicture(**picture);
+ }
+ }
+
+private:
+ typedef SampleView INHERITED;
+};
+
+SampleView* CreateSamplePictFileView(const char filename[]);
+SampleView* CreateSamplePictFileView(const char filename[]) {
+ return new PictFileView(filename);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+#if 0
+static SkView* MyFactory() { return new PictFileView; }
+static SkViewRegister reg(MyFactory);
+#endif
+
diff --git a/samplecode/SampleRegion.cpp b/samplecode/SampleRegion.cpp
new file mode 100644
index 0000000000..8b3a03c8c6
--- /dev/null
+++ b/samplecode/SampleRegion.cpp
@@ -0,0 +1,418 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SampleCode.h"
+#include "SkView.h"
+#include "SkCanvas.h"
+#include "SkGradientShader.h"
+#include "SkPath.h"
+#include "SkRegion.h"
+#include "SkShader.h"
+#include "SkUtils.h"
+#include "SkImageDecoder.h"
+
+static void test_strokerect(SkCanvas* canvas) {
+ int width = 100;
+ int height = 100;
+
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kA8_Config, width*2, height*2);
+ bitmap.allocPixels();
+ bitmap.eraseColor(SK_ColorTRANSPARENT);
+
+ SkScalar dx = 20;
+ SkScalar dy = 20;
+
+ SkPath path;
+ path.addRect(0.0f, 0.0f,
+ SkIntToScalar(width), SkIntToScalar(height),
+ SkPath::kCW_Direction);
+ SkRect r = SkRect::MakeWH(SkIntToScalar(width), SkIntToScalar(height));
+
+ SkCanvas c(bitmap);
+ c.translate(dx, dy);
+
+ SkPaint paint;
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setStrokeWidth(1);
+
+ // use the rect
+ c.clear(SK_ColorTRANSPARENT);
+ c.drawRect(r, paint);
+ canvas->drawBitmap(bitmap, 0, 0, NULL);
+
+ // use the path
+ c.clear(SK_ColorTRANSPARENT);
+ c.drawPath(path, paint);
+ canvas->drawBitmap(bitmap, SkIntToScalar(2*width), 0, NULL);
+}
+
+static void drawFadingText(SkCanvas* canvas,
+ const char* text, size_t len, SkScalar x, SkScalar y,
+ const SkPaint& paint) {
+ // Need a bounds for the text
+ SkRect bounds;
+ SkPaint::FontMetrics fm;
+
+ paint.getFontMetrics(&fm);
+ bounds.set(x, y + fm.fTop, x + paint.measureText(text, len), y + fm.fBottom);
+
+ // may need to outset bounds a little, to account for hinting and/or
+ // antialiasing
+ bounds.inset(-SkIntToScalar(2), -SkIntToScalar(2));
+
+ canvas->saveLayer(&bounds, NULL);
+ canvas->drawText(text, len, x, y, paint);
+
+ const SkPoint pts[] = {
+ { bounds.fLeft, y },
+ { bounds.fRight, y }
+ };
+ const SkColor colors[] = { SK_ColorBLACK, SK_ColorBLACK, 0 };
+
+ // pos[1] value is where we start to fade, relative to the width
+ // of our pts[] array.
+ const SkScalar pos[] = { 0, SkFloatToScalar(0.9f), SK_Scalar1 };
+
+ SkShader* s = SkGradientShader::CreateLinear(pts, colors, pos, 3,
+ SkShader::kClamp_TileMode);
+ SkPaint p;
+ p.setShader(s)->unref();
+ p.setXfermodeMode(SkXfermode::kDstIn_Mode);
+ canvas->drawRect(bounds, p);
+
+ canvas->restore();
+}
+
+static void test_text(SkCanvas* canvas) {
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setTextSize(20);
+
+ const char* str = "Hamburgefons";
+ size_t len = strlen(str);
+ SkScalar x = 20;
+ SkScalar y = 20;
+
+ canvas->drawText(str, len, x, y, paint);
+
+ y += 20;
+
+ const SkPoint pts[] = { { x, y }, { x + paint.measureText(str, len), y } };
+ const SkColor colors[] = { SK_ColorBLACK, SK_ColorBLACK, 0 };
+ const SkScalar pos[] = { 0, 0.9f, 1 };
+ SkShader* s = SkGradientShader::CreateLinear(pts, colors, pos,
+ SK_ARRAY_COUNT(colors),
+ SkShader::kClamp_TileMode);
+ paint.setShader(s)->unref();
+ canvas->drawText(str, len, x, y, paint);
+
+ y += 20;
+ paint.setShader(NULL);
+ drawFadingText(canvas, str, len, x, y, paint);
+}
+
+#ifdef SK_BUILD_FOR_WIN
+// windows doesn't have roundf
+inline float roundf(float x) { return (x-floor(x))>0.5 ? ceil(x) : floor(x); }
+#endif
+
+#ifdef SK_DEBUG
+static void make_rgn(SkRegion* rgn, int left, int top, int right, int bottom,
+ size_t count, int32_t runs[]) {
+ SkIRect r;
+ r.set(left, top, right, bottom);
+
+ rgn->debugSetRuns(runs, count);
+ SkASSERT(rgn->getBounds() == r);
+}
+
+static void test_union_bug_1505668(SkRegion* ra, SkRegion* rb, SkRegion* rc) {
+ static int32_t dataA[] = {
+ 0x00000001,
+ 0x000001dd, 2, 0x00000001, 0x0000000c, 0x0000000d, 0x00000025, 0x7fffffff,
+ 0x000001de, 1, 0x00000001, 0x00000025, 0x7fffffff,
+ 0x000004b3, 1, 0x00000001, 0x00000026, 0x7fffffff,
+ 0x000004b4, 1, 0x0000000c, 0x00000026, 0x7fffffff,
+ 0x00000579, 1, 0x00000000, 0x0000013a, 0x7fffffff,
+ 0x000005d8, 1, 0x00000000, 0x0000013b, 0x7fffffff,
+ 0x7fffffff
+ };
+ make_rgn(ra, 0, 1, 315, 1496, SK_ARRAY_COUNT(dataA), dataA);
+
+ static int32_t dataB[] = {
+ 0x000000b6,
+ 0x000000c4, 1, 0x000000a1, 0x000000f0, 0x7fffffff,
+ 0x000000d6, 0, 0x7fffffff,
+ 0x000000e4, 2, 0x00000070, 0x00000079, 0x000000a1, 0x000000b0, 0x7fffffff,
+ 0x000000e6, 0, 0x7fffffff,
+ 0x000000f4, 2, 0x00000070, 0x00000079, 0x000000a1, 0x000000b0, 0x7fffffff,
+ 0x000000f6, 0, 0x7fffffff,
+ 0x00000104, 1, 0x000000a1, 0x000000b0, 0x7fffffff,
+ 0x7fffffff
+ };
+ make_rgn(rb, 112, 182, 240, 260, SK_ARRAY_COUNT(dataB), dataB);
+
+ rc->op(*ra, *rb, SkRegion::kUnion_Op);
+}
+#endif
+
+static void scale_rect(SkIRect* dst, const SkIRect& src, float scale) {
+ dst->fLeft = (int)::roundf(src.fLeft * scale);
+ dst->fTop = (int)::roundf(src.fTop * scale);
+ dst->fRight = (int)::roundf(src.fRight * scale);
+ dst->fBottom = (int)::roundf(src.fBottom * scale);
+}
+
+static void scale_rgn(SkRegion* dst, const SkRegion& src, float scale) {
+ SkRegion tmp;
+ SkRegion::Iterator iter(src);
+
+ for (; !iter.done(); iter.next()) {
+ SkIRect r;
+ scale_rect(&r, iter.rect(), scale);
+ tmp.op(r, SkRegion::kUnion_Op);
+ }
+ dst->swap(tmp);
+}
+
+static void paint_rgn(SkCanvas* canvas, const SkRegion& rgn,
+ const SkPaint& paint) {
+ SkRegion scaled;
+ scale_rgn(&scaled, rgn, 0.5f);
+
+ SkRegion::Iterator iter(rgn);
+
+ for (; !iter.done(); iter.next())
+ {
+ SkRect r;
+ r.set(iter.rect());
+ canvas->drawRect(r, paint);
+ }
+}
+
+class RegionView : public SampleView {
+public:
+ RegionView() {
+ fBase.set(100, 100, 150, 150);
+ fRect = fBase;
+ fRect.inset(5, 5);
+ fRect.offset(25, 25);
+ this->setBGColor(0xFFDDDDDD);
+ }
+
+ void build_base_rgn(SkRegion* rgn) {
+ rgn->setRect(fBase);
+ SkIRect r = fBase;
+ r.offset(75, 20);
+ rgn->op(r, SkRegion::kUnion_Op);
+ }
+
+ void build_rgn(SkRegion* rgn, SkRegion::Op op) {
+ build_base_rgn(rgn);
+ rgn->op(fRect, op);
+ }
+
+
+protected:
+ // overrides from SkEventSink
+ virtual bool onQuery(SkEvent* evt) {
+ if (SampleCode::TitleQ(*evt)) {
+ SampleCode::TitleR(evt, "Regions");
+ return true;
+ }
+ return this->INHERITED::onQuery(evt);
+ }
+
+ static void drawstr(SkCanvas* canvas, const char text[], const SkPoint& loc,
+ bool hilite) {
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setTextSize(SkIntToScalar(20));
+ paint.setColor(hilite ? SK_ColorRED : 0x40FF0000);
+ canvas->drawText(text, strlen(text), loc.fX, loc.fY, paint);
+ }
+
+ void drawPredicates(SkCanvas* canvas, const SkPoint pts[]) {
+ SkRegion rgn;
+ build_base_rgn(&rgn);
+
+ drawstr(canvas, "Intersects", pts[0], rgn.intersects(fRect));
+ drawstr(canvas, "Contains", pts[1], rgn.contains(fRect));
+ }
+
+ void drawOrig(SkCanvas* canvas, bool bg) {
+ SkRect r;
+ SkPaint paint;
+
+ paint.setStyle(SkPaint::kStroke_Style);
+ if (bg)
+ paint.setColor(0xFFBBBBBB);
+
+ SkRegion rgn;
+ build_base_rgn(&rgn);
+ paint_rgn(canvas, rgn, paint);
+
+ r.set(fRect);
+ canvas->drawRect(r, paint);
+ }
+
+ void drawRgnOped(SkCanvas* canvas, SkRegion::Op op, SkColor color) {
+ SkRegion rgn;
+
+ this->build_rgn(&rgn, op);
+
+ {
+ SkRegion tmp, tmp2(rgn);
+
+ tmp = tmp2;
+ tmp.translate(5, -3);
+
+ {
+ char buffer[1000];
+ size_t size = tmp.writeToMemory(NULL);
+ SkASSERT(size <= sizeof(buffer));
+ size_t size2 = tmp.writeToMemory(buffer);
+ SkASSERT(size == size2);
+
+ SkRegion tmp3;
+ size2 = tmp3.readFromMemory(buffer);
+ SkASSERT(size == size2);
+
+ SkASSERT(tmp3 == tmp);
+ }
+
+ rgn.translate(20, 30, &tmp);
+ SkASSERT(rgn.isEmpty() || tmp != rgn);
+ tmp.translate(-20, -30);
+ SkASSERT(tmp == rgn);
+ }
+
+ this->drawOrig(canvas, true);
+
+ SkPaint paint;
+ paint.setColor((color & ~(0xFF << 24)) | (0x44 << 24));
+ paint_rgn(canvas, rgn, paint);
+
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setColor(color);
+ paint_rgn(canvas, rgn, paint);
+ }
+
+ void drawPathOped(SkCanvas* canvas, SkRegion::Op op, SkColor color) {
+ SkRegion rgn;
+ SkPath path;
+
+ this->build_rgn(&rgn, op);
+ rgn.getBoundaryPath(&path);
+
+ this->drawOrig(canvas, true);
+
+ SkPaint paint;
+
+ paint.setStyle(SkPaint::kFill_Style);
+ paint.setColor((color & ~(0xFF << 24)) | (0x44 << 24));
+ canvas->drawPath(path, paint);
+ paint.setColor(color);
+ paint.setStyle(SkPaint::kStroke_Style);
+ canvas->drawPath(path, paint);
+ }
+
+ virtual void onDrawContent(SkCanvas* canvas) {
+ if (false) { // avoid bit rot, suppress warning
+ test_strokerect(canvas);
+ return;
+ }
+ if (false) { // avoid bit rot, suppress warning
+ test_text(canvas);
+ return;
+ }
+#ifdef SK_DEBUG
+ if (true) {
+ SkRegion a, b, c;
+ test_union_bug_1505668(&a, &b, &c);
+
+ if (false) { // draw the result of the test
+ SkPaint paint;
+
+ canvas->translate(SkIntToScalar(10), SkIntToScalar(10));
+ paint.setColor(SK_ColorRED);
+ paint_rgn(canvas, a, paint);
+ paint.setColor(0x800000FF);
+ paint_rgn(canvas, b, paint);
+ paint.setColor(SK_ColorBLACK);
+ paint.setStyle(SkPaint::kStroke_Style);
+ // paint_rgn(canvas, c, paint);
+ return;
+ }
+ }
+#endif
+ const SkPoint origins[] = {
+ { 30*SK_Scalar1, 50*SK_Scalar1 },
+ { 150*SK_Scalar1, 50*SK_Scalar1 },
+ };
+ this->drawPredicates(canvas, origins);
+
+ static const struct {
+ SkColor fColor;
+ const char* fName;
+ SkRegion::Op fOp;
+ } gOps[] = {
+ { SK_ColorBLACK, "Difference", SkRegion::kDifference_Op },
+ { SK_ColorRED, "Intersect", SkRegion::kIntersect_Op },
+ { 0xFF008800, "Union", SkRegion::kUnion_Op },
+ { SK_ColorBLUE, "XOR", SkRegion::kXOR_Op }
+ };
+
+ SkPaint textPaint;
+ textPaint.setAntiAlias(true);
+ textPaint.setTextSize(SK_Scalar1*24);
+
+ this->drawOrig(canvas, false);
+ canvas->save();
+ canvas->translate(SkIntToScalar(200), 0);
+ this->drawRgnOped(canvas, SkRegion::kUnion_Op, SK_ColorBLACK);
+ canvas->restore();
+
+ canvas->translate(0, SkIntToScalar(200));
+
+ for (size_t op = 0; op < SK_ARRAY_COUNT(gOps); op++) {
+ canvas->drawText(gOps[op].fName, strlen(gOps[op].fName), SkIntToScalar(75), SkIntToScalar(50), textPaint);
+
+ this->drawRgnOped(canvas, gOps[op].fOp, gOps[op].fColor);
+
+ canvas->save();
+ canvas->translate(0, SkIntToScalar(200));
+ this->drawPathOped(canvas, gOps[op].fOp, gOps[op].fColor);
+ canvas->restore();
+
+ canvas->translate(SkIntToScalar(200), 0);
+ }
+ }
+
+ virtual SkView::Click* onFindClickHandler(SkScalar x, SkScalar y) {
+ return fRect.contains(SkScalarRound(x), SkScalarRound(y)) ? new Click(this) : NULL;
+ }
+
+ virtual bool onClick(Click* click) {
+ fRect.offset(click->fICurr.fX - click->fIPrev.fX,
+ click->fICurr.fY - click->fIPrev.fY);
+ this->inval(NULL);
+ return true;
+ }
+
+private:
+ SkIRect fBase, fRect;
+
+ typedef SampleView INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static SkView* MyFactory() { return new RegionView; }
+static SkViewRegister reg(MyFactory);
+
diff --git a/samplecode/SampleShaderText.cpp b/samplecode/SampleShaderText.cpp
new file mode 100644
index 0000000000..a83b09995b
--- /dev/null
+++ b/samplecode/SampleShaderText.cpp
@@ -0,0 +1,216 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SampleCode.h"
+#include "SkView.h"
+#include "SkCanvas.h"
+#include "SkGradientShader.h"
+#include "SkUnitMappers.h"
+
+static void makebm(SkBitmap* bm, SkBitmap::Config config, int w, int h) {
+ bm->setConfig(config, w, h);
+ bm->allocPixels();
+ bm->eraseColor(SK_ColorTRANSPARENT);
+
+ SkCanvas canvas(*bm);
+ SkScalar s = SkIntToScalar(w < h ? w : h);
+ SkPoint pts[] = { { 0, 0 }, { s, s } };
+ SkColor colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE };
+ SkScalar pos[] = { 0, SK_Scalar1/2, SK_Scalar1 };
+ SkPaint paint;
+
+ SkUnitMapper* um = NULL;
+
+ um = new SkCosineMapper;
+
+ SkAutoUnref au(um);
+
+ paint.setDither(true);
+ paint.setShader(SkGradientShader::CreateLinear(pts, colors, pos,
+ SK_ARRAY_COUNT(colors), SkShader::kClamp_TileMode, um))->unref();
+ canvas.drawPaint(paint);
+}
+
+static SkShader* MakeBitmapShader(SkShader::TileMode tx, SkShader::TileMode ty,
+ int w, int h) {
+ static SkBitmap bmp;
+ if (bmp.isNull()) {
+ makebm(&bmp, SkBitmap::kARGB_8888_Config, w/2, h/4);
+ }
+ return SkShader::CreateBitmapShader(bmp, tx, ty);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+struct GradData {
+ int fCount;
+ const SkColor* fColors;
+ const SkScalar* fPos;
+};
+
+static const SkColor gColors[] = {
+ SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, SK_ColorWHITE, SK_ColorBLACK
+};
+
+static const GradData gGradData[] = {
+ { 2, gColors, NULL },
+ { 5, gColors, NULL },
+};
+
+static SkShader* MakeLinear(const SkPoint pts[2], const GradData& data,
+ SkShader::TileMode tm, SkUnitMapper* mapper) {
+ return SkGradientShader::CreateLinear(pts, data.fColors, data.fPos,
+ data.fCount, tm, mapper);
+}
+
+static SkShader* MakeRadial(const SkPoint pts[2], const GradData& data,
+ SkShader::TileMode tm, SkUnitMapper* mapper) {
+ SkPoint center;
+ center.set(SkScalarAve(pts[0].fX, pts[1].fX),
+ SkScalarAve(pts[0].fY, pts[1].fY));
+ return SkGradientShader::CreateRadial(center, center.fX, data.fColors,
+ data.fPos, data.fCount, tm, mapper);
+}
+
+static SkShader* MakeSweep(const SkPoint pts[2], const GradData& data,
+ SkShader::TileMode tm, SkUnitMapper* mapper) {
+ SkPoint center;
+ center.set(SkScalarAve(pts[0].fX, pts[1].fX),
+ SkScalarAve(pts[0].fY, pts[1].fY));
+ return SkGradientShader::CreateSweep(center.fX, center.fY, data.fColors,
+ data.fPos, data.fCount, mapper);
+}
+
+static SkShader* Make2Radial(const SkPoint pts[2], const GradData& data,
+ SkShader::TileMode tm, SkUnitMapper* mapper) {
+ SkPoint center0, center1;
+ center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
+ SkScalarAve(pts[0].fY, pts[1].fY));
+ center1.set(SkScalarInterp(pts[0].fX, pts[1].fX, SkIntToScalar(3)/5),
+ SkScalarInterp(pts[0].fY, pts[1].fY, SkIntToScalar(1)/4));
+ return SkGradientShader::CreateTwoPointRadial(
+ center1, (pts[1].fX - pts[0].fX) / 7,
+ center0, (pts[1].fX - pts[0].fX) / 2,
+ data.fColors, data.fPos, data.fCount, tm, mapper);
+}
+
+typedef SkShader* (*GradMaker)(const SkPoint pts[2], const GradData& data,
+ SkShader::TileMode tm, SkUnitMapper* mapper);
+static const GradMaker gGradMakers[] = {
+ MakeLinear, MakeRadial, MakeSweep, Make2Radial
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class ShaderTextView : public SampleView {
+public:
+ ShaderTextView() {
+ this->setBGColor(0xFFDDDDDD);
+ }
+
+protected:
+ // overrides from SkEventSink
+ virtual bool onQuery(SkEvent* evt) {
+ if (SampleCode::TitleQ(*evt)) {
+ SampleCode::TitleR(evt, "Shader Text");
+ return true;
+ }
+ return this->INHERITED::onQuery(evt);
+ }
+
+ virtual void onDrawContent(SkCanvas* canvas) {
+ const char text[] = "Shaded Text";
+ const int textLen = SK_ARRAY_COUNT(text) - 1;
+ static int pointSize = 36;
+
+ int w = pointSize * textLen;
+ int h = pointSize;
+
+ SkPoint pts[2] = {
+ { 0, 0 },
+ { SkIntToScalar(w), SkIntToScalar(h) }
+ };
+ SkScalar textBase = SkIntToScalar(h/2);
+
+ SkShader::TileMode tileModes[] = {
+ SkShader::kClamp_TileMode,
+ SkShader::kRepeat_TileMode,
+ SkShader::kMirror_TileMode
+ };
+
+ static const int gradCount = SK_ARRAY_COUNT(gGradData) *
+ SK_ARRAY_COUNT(gGradMakers);
+ static const int bmpCount = SK_ARRAY_COUNT(tileModes) *
+ SK_ARRAY_COUNT(tileModes);
+ SkShader* shaders[gradCount + bmpCount];
+
+ int shdIdx = 0;
+ for (size_t d = 0; d < SK_ARRAY_COUNT(gGradData); ++d) {
+ for (size_t m = 0; m < SK_ARRAY_COUNT(gGradMakers); ++m) {
+ shaders[shdIdx++] = gGradMakers[m](pts,
+ gGradData[d],
+ SkShader::kClamp_TileMode,
+ NULL);
+ }
+ }
+ for (size_t tx = 0; tx < SK_ARRAY_COUNT(tileModes); ++tx) {
+ for (size_t ty = 0; ty < SK_ARRAY_COUNT(tileModes); ++ty) {
+ shaders[shdIdx++] = MakeBitmapShader(tileModes[tx],
+ tileModes[ty],
+ w/8, h);
+ }
+ }
+
+ SkPaint paint;
+ paint.setDither(true);
+ paint.setAntiAlias(true);
+ paint.setTextSize(SkIntToScalar(pointSize));
+
+ canvas->save();
+ canvas->translate(SkIntToScalar(20), SkIntToScalar(10));
+
+ SkPath path;
+ path.arcTo(SkRect::MakeXYWH(SkIntToScalar(-40), SkIntToScalar(15),
+ SkIntToScalar(300), SkIntToScalar(90)),
+ SkIntToScalar(225), SkIntToScalar(90),
+ false);
+ path.close();
+
+ static const int testsPerCol = 8;
+ static const int rowHeight = 60;
+ static const int colWidth = 300;
+ canvas->save();
+ for (size_t s = 0; s < SK_ARRAY_COUNT(shaders); s++) {
+ canvas->save();
+ int i = 2*s;
+ canvas->translate(SkIntToScalar((i / testsPerCol) * colWidth),
+ SkIntToScalar((i % testsPerCol) * rowHeight));
+ paint.setShader(shaders[s])->unref();
+ canvas->drawText(text, textLen, 0, textBase, paint);
+ canvas->restore();
+ canvas->save();
+ ++i;
+ canvas->translate(SkIntToScalar((i / testsPerCol) * colWidth),
+ SkIntToScalar((i % testsPerCol) * rowHeight));
+ canvas->drawTextOnPath(text, textLen, path, NULL, paint);
+ canvas->restore();
+ }
+ canvas->restore();
+
+ canvas->translate(0, SkIntToScalar(370));
+ this->inval(NULL);
+ }
+
+private:
+ typedef SampleView INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+static SkView* MyFactory() { return new ShaderTextView; }
+static SkViewRegister reg(MyFactory);
+
diff --git a/samplecode/SampleStrokeText.cpp b/samplecode/SampleStrokeText.cpp
new file mode 100644
index 0000000000..4ceb0d5548
--- /dev/null
+++ b/samplecode/SampleStrokeText.cpp
@@ -0,0 +1,149 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SampleCode.h"
+#include "SkView.h"
+#include "SkCanvas.h"
+#include "Sk64.h"
+#include "SkGradientShader.h"
+#include "SkGraphics.h"
+#include "SkImageDecoder.h"
+#include "SkKernel33MaskFilter.h"
+#include "SkPath.h"
+#include "SkRandom.h"
+#include "SkRegion.h"
+#include "SkShader.h"
+#include "SkUtils.h"
+#include "SkColorPriv.h"
+#include "SkColorFilter.h"
+#include "SkTime.h"
+#include "SkTypeface.h"
+#include "SkXfermode.h"
+
+static void lettersToBitmap(SkBitmap* dst, const char chars[],
+ const SkPaint& original, SkBitmap::Config config) {
+ SkPath path;
+ SkScalar x = 0;
+ SkScalar width;
+ SkPath p;
+ for (size_t i = 0; i < strlen(chars); i++) {
+ original.getTextPath(&chars[i], 1, x, 0, &p);
+ path.addPath(p);
+ original.getTextWidths(&chars[i], 1, &width);
+ x += width;
+ }
+ SkRect bounds = path.getBounds();
+ SkScalar sw = -original.getStrokeWidth();
+ bounds.inset(sw, sw);
+ path.offset(-bounds.fLeft, -bounds.fTop);
+ bounds.offset(-bounds.fLeft, -bounds.fTop);
+
+ int w = SkScalarRound(bounds.width());
+ int h = SkScalarRound(bounds.height());
+ SkPaint paint(original);
+ SkBitmap src;
+ src.setConfig(config, w, h);
+ src.allocPixels();
+ src.eraseColor(SK_ColorTRANSPARENT);
+ {
+ SkCanvas canvas(src);
+ paint.setAntiAlias(true);
+ paint.setColor(SK_ColorBLACK);
+ paint.setStyle(SkPaint::kFill_Style);
+ canvas.drawPath(path, paint);
+ }
+
+ dst->setConfig(config, w, h);
+ dst->allocPixels();
+ dst->eraseColor(SK_ColorWHITE);
+ {
+ SkCanvas canvas(*dst);
+ paint.setXfermodeMode(SkXfermode::kDstATop_Mode);
+ canvas.drawBitmap(src, 0, 0, &paint);
+ paint.setColor(original.getColor());
+ paint.setStyle(SkPaint::kStroke_Style);
+ canvas.drawPath(path, paint);
+ }
+}
+
+static void lettersToBitmap2(SkBitmap* dst, const char chars[],
+ const SkPaint& original, SkBitmap::Config config) {
+ SkPath path;
+ SkScalar x = 0;
+ SkScalar width;
+ SkPath p;
+ for (size_t i = 0; i < strlen(chars); i++) {
+ original.getTextPath(&chars[i], 1, x, 0, &p);
+ path.addPath(p);
+ original.getTextWidths(&chars[i], 1, &width);
+ x += width;
+ }
+ SkRect bounds = path.getBounds();
+ SkScalar sw = -original.getStrokeWidth();
+ bounds.inset(sw, sw);
+ path.offset(-bounds.fLeft, -bounds.fTop);
+ bounds.offset(-bounds.fLeft, -bounds.fTop);
+
+ int w = SkScalarRound(bounds.width());
+ int h = SkScalarRound(bounds.height());
+ SkPaint paint(original);
+
+ paint.setAntiAlias(true);
+ paint.setXfermodeMode(SkXfermode::kDstATop_Mode);
+ paint.setColor(original.getColor());
+ paint.setStyle(SkPaint::kStroke_Style);
+
+ dst->setConfig(config, w, h);
+ dst->allocPixels();
+ dst->eraseColor(SK_ColorWHITE);
+
+ SkCanvas canvas(*dst);
+ canvas.drawPath(path, paint);
+}
+
+class StrokeTextView : public SampleView {
+ bool fAA;
+public:
+ StrokeTextView() : fAA(false) {
+ this->setBGColor(0xFFCC8844);
+ }
+
+protected:
+ // overrides from SkEventSink
+ virtual bool onQuery(SkEvent* evt) {
+ if (SampleCode::TitleQ(*evt)) {
+ SampleCode::TitleR(evt, "StrokeText");
+ return true;
+ }
+ return this->INHERITED::onQuery(evt);
+ }
+
+ virtual void onDrawContent(SkCanvas* canvas) {
+ SkBitmap bm;
+ SkPaint paint;
+
+ paint.setStrokeWidth(SkIntToScalar(6));
+ paint.setTextSize(SkIntToScalar(80));
+// paint.setTypeface(Typeface.DEFAULT_BOLD);
+
+ lettersToBitmap(&bm, "Test Case", paint, SkBitmap::kARGB_4444_Config);
+ if (false) { // avoid bit rot, suppress warning
+ lettersToBitmap2(&bm, "Test Case", paint, SkBitmap::kARGB_4444_Config);
+ }
+ canvas->drawBitmap(bm, 0, 0);
+ }
+
+private:
+
+ typedef SampleView INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static SkView* MyFactory() { return new StrokeTextView; }
+static SkViewRegister reg(MyFactory);
+
diff --git a/samplecode/SampleText.cpp b/samplecode/SampleText.cpp
new file mode 100644
index 0000000000..648321870a
--- /dev/null
+++ b/samplecode/SampleText.cpp
@@ -0,0 +1,330 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SampleCode.h"
+#include "SkView.h"
+#include "SkCanvas.h"
+#include "Sk64.h"
+#include "SkFlattenableBuffers.h"
+#include "SkGradientShader.h"
+#include "SkGraphics.h"
+#include "SkImageDecoder.h"
+#include "SkKernel33MaskFilter.h"
+#include "SkPath.h"
+#include "SkRandom.h"
+#include "SkRegion.h"
+#include "SkShader.h"
+#include "SkUtils.h"
+#include "SkColorPriv.h"
+#include "SkColorFilter.h"
+#include "SkTime.h"
+#include "SkTypeface.h"
+#include "SkXfermode.h"
+
+#include "SkStream.h"
+#include "SkXMLParser.h"
+
+class ReduceNoise : public SkKernel33ProcMaskFilter {
+public:
+ ReduceNoise(int percent256) : SkKernel33ProcMaskFilter(percent256) {}
+ virtual uint8_t computeValue(uint8_t* const* srcRows) const {
+ int c = srcRows[1][1];
+ int min = 255, max = 0;
+ for (int i = 0; i < 3; i++)
+ for (int j = 0; j < 3; j++)
+ if (i != 1 || j != 1)
+ {
+ int v = srcRows[i][j];
+ if (max < v)
+ max = v;
+ if (min > v)
+ min = v;
+ }
+ if (c > max) c = max;
+ // if (c < min) c = min;
+ return c;
+ }
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(ReduceNoise)
+
+private:
+ ReduceNoise(SkFlattenableReadBuffer& rb) : SkKernel33ProcMaskFilter(rb) {}
+};
+
+class Darken : public SkKernel33ProcMaskFilter {
+public:
+ Darken(int percent256) : SkKernel33ProcMaskFilter(percent256) {}
+ virtual uint8_t computeValue(uint8_t* const* srcRows) const {
+ int c = srcRows[1][1];
+ float f = c / 255.f;
+
+ if (c >= 0) {
+ f = sqrtf(f);
+ } else {
+ f *= f;
+ }
+ SkASSERT(f >= 0 && f <= 1);
+ return (int)(f * 255);
+ }
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Darken)
+
+private:
+ Darken(SkFlattenableReadBuffer& rb) : SkKernel33ProcMaskFilter(rb) {}
+};
+
+static SkMaskFilter* makemf() { return new Darken(0x30); }
+
+static void test_breakText() {
+ SkPaint paint;
+ const char* text = "sdfkljAKLDFJKEWkldfjlk#$%&sdfs.dsj";
+ size_t length = strlen(text);
+ SkScalar width = paint.measureText(text, length);
+
+ SkScalar mm = 0;
+ SkScalar nn = 0;
+ for (SkScalar w = 0; w <= width; w += SK_Scalar1) {
+ SkScalar m;
+ size_t n = paint.breakText(text, length, w, &m,
+ SkPaint::kBackward_TextBufferDirection);
+
+ SkASSERT(n <= length);
+ SkASSERT(m <= width);
+
+ if (n == 0) {
+ SkASSERT(m == 0);
+ } else {
+ // now assert that we're monotonic
+ if (n == nn) {
+ SkASSERT(m == mm);
+ } else {
+ SkASSERT(n > nn);
+ SkASSERT(m > mm);
+ }
+ }
+ nn = SkIntToScalar((unsigned int)n);
+ mm = m;
+ }
+
+ SkDEBUGCODE(size_t length2 =) paint.breakText(text, length, width, &mm);
+ SkASSERT(length2 == length);
+ SkASSERT(mm == width);
+}
+
+static SkRandom gRand;
+
+class SkPowerMode : public SkXfermode {
+public:
+ SkPowerMode(SkScalar exponent) { this->init(exponent); }
+
+ virtual void xfer16(uint16_t dst[], const SkPMColor src[], int count,
+ const SkAlpha aa[]);
+
+ typedef SkFlattenable* (*Factory)(SkFlattenableReadBuffer&);
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkPowerMode)
+
+private:
+ SkScalar fExp; // user's value
+ uint8_t fTable[256]; // cache
+
+ void init(SkScalar exponent);
+ SkPowerMode(SkFlattenableReadBuffer& b) : INHERITED(b) {
+ // read the exponent
+ this->init(SkFixedToScalar(b.readFixed()));
+ }
+ virtual void flatten(SkFlattenableWriteBuffer& b) const SK_OVERRIDE {
+ this->INHERITED::flatten(b);
+ b.writeFixed(SkScalarToFixed(fExp));
+ }
+
+ typedef SkXfermode INHERITED;
+};
+
+void SkPowerMode::init(SkScalar e) {
+ fExp = e;
+ float ee = SkScalarToFloat(e);
+
+ printf("------ %g\n", ee);
+ for (int i = 0; i < 256; i++) {
+ float x = i / 255.f;
+ // printf(" %d %g", i, x);
+ x = powf(x, ee);
+ // printf(" %g", x);
+ int xx = SkScalarRound(SkFloatToScalar(x * 255));
+ // printf(" %d\n", xx);
+ fTable[i] = SkToU8(xx);
+ }
+}
+
+void SkPowerMode::xfer16(uint16_t dst[], const SkPMColor src[], int count,
+ const SkAlpha aa[]) {
+ for (int i = 0; i < count; i++) {
+ SkPMColor c = src[i];
+ int r = SkGetPackedR32(c);
+ int g = SkGetPackedG32(c);
+ int b = SkGetPackedB32(c);
+ r = fTable[r];
+ g = fTable[g];
+ b = fTable[b];
+ dst[i] = SkPack888ToRGB16(r, g, b);
+ }
+}
+
+static const struct {
+ const char* fName;
+ uint32_t fFlags;
+ bool fFlushCache;
+} gHints[] = {
+ { "Linear", SkPaint::kLinearText_Flag, false },
+ { "Normal", 0, true },
+ { "Subpixel", SkPaint::kSubpixelText_Flag, true }
+};
+
+static void DrawTheText(SkCanvas* canvas, const char text[], size_t length,
+ SkScalar x, SkScalar y, const SkPaint& paint,
+ SkScalar clickX, SkMaskFilter* mf) {
+ SkPaint p(paint);
+
+#if 0
+ canvas->drawText(text, length, x, y, paint);
+#else
+ {
+ SkPoint pts[1000];
+ SkScalar xpos = x;
+ SkASSERT(length <= SK_ARRAY_COUNT(pts));
+ for (size_t i = 0; i < length; i++) {
+ pts[i].set(xpos, y), xpos += paint.getTextSize();
+ }
+ canvas->drawPosText(text, length, pts, paint);
+ }
+#endif
+
+ p.setSubpixelText(true);
+ x += SkIntToScalar(180);
+ canvas->drawText(text, length, x, y, p);
+
+#ifdef SK_DEBUG
+ if (true) {
+ // p.setMaskFilter(mf);
+ p.setSubpixelText(false);
+ p.setLinearText(true);
+ x += SkIntToScalar(180);
+ canvas->drawText(text, length, x, y, p);
+ }
+#endif
+}
+
+class TextSpeedView : public SampleView {
+public:
+ TextSpeedView() {
+ fMF = makemf();
+
+ fHints = 0;
+ fClickX = 0;
+
+ test_breakText();
+ }
+
+ virtual ~TextSpeedView() {
+ SkSafeUnref(fMF);
+ }
+
+protected:
+ // overrides from SkEventSink
+ virtual bool onQuery(SkEvent* evt) {
+ if (SampleCode::TitleQ(*evt)) {
+ SampleCode::TitleR(evt, "Text");
+ return true;
+ }
+ return this->INHERITED::onQuery(evt);
+ }
+
+ static void make_textstrip(SkBitmap* bm) {
+ bm->setConfig(SkBitmap::kRGB_565_Config, 200, 18);
+ bm->allocPixels();
+ bm->eraseColor(SK_ColorWHITE);
+
+ SkCanvas canvas(*bm);
+ SkPaint paint;
+ const char* s = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit";
+
+ paint.setFlags(paint.getFlags() | SkPaint::kAntiAlias_Flag
+ | SkPaint::kDevKernText_Flag);
+ paint.setTextSize(SkIntToScalar(14));
+ canvas.drawText(s, strlen(s), SkIntToScalar(8), SkIntToScalar(14), paint);
+ }
+
+ static void fill_pts(SkPoint pts[], size_t n, SkRandom* rand) {
+ for (size_t i = 0; i < n; i++)
+ pts[i].set(rand->nextUScalar1() * 640, rand->nextUScalar1() * 480);
+ }
+
+ virtual void onDrawContent(SkCanvas* canvas) {
+ SkAutoCanvasRestore restore(canvas, false);
+ {
+ SkRect r;
+ r.set(0, 0, SkIntToScalar(1000), SkIntToScalar(20));
+ // canvas->saveLayer(&r, NULL, SkCanvas::kHasAlphaLayer_SaveFlag);
+ }
+
+ SkPaint paint;
+// const uint16_t glyphs[] = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 };
+ int index = fHints % SK_ARRAY_COUNT(gHints);
+ index = 1;
+// const char* style = gHints[index].fName;
+
+// canvas->translate(0, SkIntToScalar(50));
+
+ // canvas->drawText(style, strlen(style), SkIntToScalar(20), SkIntToScalar(20), paint);
+
+ SkSafeUnref(paint.setTypeface(SkTypeface::CreateFromFile("/skimages/samplefont.ttf")));
+ paint.setAntiAlias(true);
+ paint.setFlags(paint.getFlags() | gHints[index].fFlags);
+
+ SkRect clip;
+ clip.set(SkIntToScalar(25), SkIntToScalar(34), SkIntToScalar(88), SkIntToScalar(155));
+
+ const char* text = "Hamburgefons";
+ size_t length = strlen(text);
+
+ SkScalar y = SkIntToScalar(0);
+ for (int i = 9; i <= 24; i++) {
+ paint.setTextSize(SkIntToScalar(i) /*+ (gRand.nextU() & 0xFFFF)*/);
+ for (SkScalar dx = 0; dx <= SkIntToScalar(3)/4;
+ dx += SkIntToScalar(1) /* /4 */) {
+ y += paint.getFontSpacing();
+ DrawTheText(canvas, text, length, SkIntToScalar(20) + dx, y,
+ paint, fClickX, fMF);
+ }
+ }
+ if (gHints[index].fFlushCache) {
+// SkGraphics::SetFontCacheUsed(0);
+ }
+ }
+
+ virtual SkView::Click* onFindClickHandler(SkScalar x, SkScalar y) {
+ fClickX = x;
+ this->inval(NULL);
+ return this->INHERITED::onFindClickHandler(x, y);
+ }
+
+ virtual bool onClick(Click* click) {
+ return this->INHERITED::onClick(click);
+ }
+
+private:
+ int fHints;
+ SkScalar fClickX;
+ SkMaskFilter* fMF;
+
+ typedef SampleView INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static SkView* MyFactory() { return new TextSpeedView; }
+static SkViewRegister reg(MyFactory);
+
diff --git a/samplecode/SampleTiling.cpp b/samplecode/SampleTiling.cpp
new file mode 100644
index 0000000000..59e88ea9a2
--- /dev/null
+++ b/samplecode/SampleTiling.cpp
@@ -0,0 +1,174 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SampleCode.h"
+#include "SkView.h"
+#include "SkCanvas.h"
+#include "SkPaint.h"
+#include "SkPath.h"
+#include "SkRegion.h"
+#include "SkShader.h"
+#include "SkUtils.h"
+#include "SkColorPriv.h"
+#include "SkColorFilter.h"
+#include "SkPicture.h"
+#include "SkTypeface.h"
+
+// effects
+#include "SkGradientShader.h"
+#include "SkUnitMappers.h"
+#include "SkBlurDrawLooper.h"
+
+static void makebm(SkBitmap* bm, SkBitmap::Config config, int w, int h) {
+ bm->setConfig(config, w, h);
+ bm->allocPixels();
+ bm->eraseColor(SK_ColorTRANSPARENT);
+
+ SkCanvas canvas(*bm);
+ SkPoint pts[] = { { 0, 0 }, { SkIntToScalar(w), SkIntToScalar(h) } };
+ SkColor colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE };
+ SkScalar pos[] = { 0, SK_Scalar1/2, SK_Scalar1 };
+ SkPaint paint;
+
+ SkUnitMapper* um = NULL;
+
+ um = new SkCosineMapper;
+// um = new SkDiscreteMapper(12);
+
+ SkAutoUnref au(um);
+
+ paint.setDither(true);
+ paint.setShader(SkGradientShader::CreateLinear(pts, colors, pos,
+ SK_ARRAY_COUNT(colors), SkShader::kClamp_TileMode, um))->unref();
+ canvas.drawPaint(paint);
+}
+
+static void setup(SkPaint* paint, const SkBitmap& bm, bool filter,
+ SkShader::TileMode tmx, SkShader::TileMode tmy) {
+ SkShader* shader = SkShader::CreateBitmapShader(bm, tmx, tmy);
+ paint->setShader(shader)->unref();
+ paint->setFilterBitmap(filter);
+}
+
+static const SkBitmap::Config gConfigs[] = {
+ SkBitmap::kARGB_8888_Config,
+ SkBitmap::kRGB_565_Config,
+ SkBitmap::kARGB_4444_Config
+};
+static const int gWidth = 32;
+static const int gHeight = 32;
+
+class TilingView : public SampleView {
+ SkPicture* fTextPicture;
+ SkBlurDrawLooper fLooper;
+public:
+ TilingView()
+ : fLooper(SkIntToScalar(1), SkIntToScalar(2), SkIntToScalar(2),
+ 0x88000000) {
+ fTextPicture = new SkPicture();
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gConfigs); i++) {
+ makebm(&fTexture[i], gConfigs[i], gWidth, gHeight);
+ }
+ }
+
+ ~TilingView() {
+ fTextPicture->unref();
+ }
+
+ SkBitmap fTexture[SK_ARRAY_COUNT(gConfigs)];
+
+protected:
+ // overrides from SkEventSink
+ virtual bool onQuery(SkEvent* evt) {
+ if (SampleCode::TitleQ(*evt)) {
+ SampleCode::TitleR(evt, "Tiling");
+ return true;
+ }
+ return this->INHERITED::onQuery(evt);
+ }
+
+ virtual void onDrawContent(SkCanvas* canvas) {
+ SkRect r = { 0, 0, SkIntToScalar(gWidth*2), SkIntToScalar(gHeight*2) };
+
+ static const char* gConfigNames[] = { "8888", "565", "4444" };
+
+ static const bool gFilters[] = { false, true };
+ static const char* gFilterNames[] = { "point", "bilinear" };
+
+ static const SkShader::TileMode gModes[] = { SkShader::kClamp_TileMode, SkShader::kRepeat_TileMode, SkShader::kMirror_TileMode };
+ static const char* gModeNames[] = { "C", "R", "M" };
+
+ SkScalar y = SkIntToScalar(24);
+ SkScalar x = SkIntToScalar(10);
+
+ SkCanvas* textCanvas = NULL;
+ if (fTextPicture->width() == 0) {
+ textCanvas = fTextPicture->beginRecording(1000, 1000);
+ }
+
+ if (textCanvas) {
+ for (size_t kx = 0; kx < SK_ARRAY_COUNT(gModes); kx++) {
+ for (size_t ky = 0; ky < SK_ARRAY_COUNT(gModes); ky++) {
+ SkPaint p;
+ SkString str;
+ p.setAntiAlias(true);
+ p.setDither(true);
+ p.setLooper(&fLooper);
+ str.printf("[%s,%s]", gModeNames[kx], gModeNames[ky]);
+
+ p.setTextAlign(SkPaint::kCenter_Align);
+ textCanvas->drawText(str.c_str(), str.size(), x + r.width()/2, y, p);
+
+ x += r.width() * 4 / 3;
+ }
+ }
+ }
+
+ y += SkIntToScalar(16);
+
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gConfigs); i++) {
+ for (size_t j = 0; j < SK_ARRAY_COUNT(gFilters); j++) {
+ x = SkIntToScalar(10);
+ for (size_t kx = 0; kx < SK_ARRAY_COUNT(gModes); kx++) {
+ for (size_t ky = 0; ky < SK_ARRAY_COUNT(gModes); ky++) {
+ SkPaint paint;
+ setup(&paint, fTexture[i], gFilters[j], gModes[kx], gModes[ky]);
+ paint.setDither(true);
+
+ canvas->save();
+ canvas->translate(x, y);
+ canvas->drawRect(r, paint);
+ canvas->restore();
+
+ x += r.width() * 4 / 3;
+ }
+ }
+ if (textCanvas) {
+ SkPaint p;
+ SkString str;
+ p.setAntiAlias(true);
+ p.setLooper(&fLooper);
+ str.printf("%s, %s", gConfigNames[i], gFilterNames[j]);
+ textCanvas->drawText(str.c_str(), str.size(), x, y + r.height() * 2 / 3, p);
+ }
+
+ y += r.height() * 4 / 3;
+ }
+ }
+
+ canvas->drawPicture(*fTextPicture);
+ }
+
+private:
+ typedef SampleView INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static SkView* MyFactory() { return new TilingView; }
+static SkViewRegister reg(MyFactory);
+
diff --git a/skia.gyp b/skia.gyp
new file mode 100644
index 0000000000..34c0528709
--- /dev/null
+++ b/skia.gyp
@@ -0,0 +1,28 @@
+# Top-level gyp configuration for Skia.
+#
+# Projects that use Skia should depend on one or more of the targets
+# defined here.
+#
+# More targets are defined within the gyp/ directory, but those are
+# not intended for external use and may change without notice.
+#
+# Full documentation at https://sites.google.com/site/skiadocs/
+#
+{
+ 'targets': [
+ {
+ 'target_name': 'alltargets',
+ 'type': 'none',
+ 'dependencies': [
+ 'gyp/everything.gyp:everything',
+ 'gyp/most.gyp:most',
+ ],
+ },
+ ],
+}
+
+# Local Variables:
+# tab-width:2
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=2 shiftwidth=2:
diff --git a/src/animator/SkDisplayEvent.cpp b/src/animator/SkDisplayEvent.cpp
index 303e42c662..52c874b68a 100644
--- a/src/animator/SkDisplayEvent.cpp
+++ b/src/animator/SkDisplayEvent.cpp
@@ -52,7 +52,7 @@ SkDisplayEvent::~SkDisplayEvent() {
deleteMembers();
}
-bool SkDisplayEvent::add(SkAnimateMaker& , SkDisplayable* child) {
+bool SkDisplayEvent::addChild(SkAnimateMaker& , SkDisplayable* child) {
*fChildren.append() = child;
return true;
}
diff --git a/src/animator/SkDisplayEvent.h b/src/animator/SkDisplayEvent.h
index ef8ec68b9b..5701da2ce3 100644
--- a/src/animator/SkDisplayEvent.h
+++ b/src/animator/SkDisplayEvent.h
@@ -34,7 +34,7 @@ class SkDisplayEvent : public SkDisplayable {
};
SkDisplayEvent();
virtual ~SkDisplayEvent();
- virtual bool add(SkAnimateMaker& , SkDisplayable* child) SK_OVERRIDE;
+ virtual bool addChild(SkAnimateMaker& , SkDisplayable* child) SK_OVERRIDE;
virtual bool contains(SkDisplayable*);
virtual SkDisplayable* contains(const SkString& );
#ifdef SK_DEBUG
diff --git a/src/animator/SkDisplayPost.cpp b/src/animator/SkDisplayPost.cpp
index 3c9fa19e46..a50d5cce93 100644
--- a/src/animator/SkDisplayPost.cpp
+++ b/src/animator/SkDisplayPost.cpp
@@ -47,7 +47,7 @@ SkPost::~SkPost() {
delete *part;
}
-bool SkPost::add(SkAnimateMaker& , SkDisplayable* child) {
+bool SkPost::addChild(SkAnimateMaker& , SkDisplayable* child) {
SkASSERT(child && child->isDataInput());
SkDataInput* part = (SkDataInput*) child;
*fParts.append() = part;
diff --git a/src/animator/SkDisplayPost.h b/src/animator/SkDisplayPost.h
index 57ae31cce5..cd22306840 100644
--- a/src/animator/SkDisplayPost.h
+++ b/src/animator/SkDisplayPost.h
@@ -27,7 +27,7 @@ class SkPost : public SkDisplayable {
};
SkPost();
virtual ~SkPost();
- virtual bool add(SkAnimateMaker& , SkDisplayable* child) SK_OVERRIDE;
+ virtual bool addChild(SkAnimateMaker& , SkDisplayable* child) SK_OVERRIDE;
virtual bool childrenNeedDisposing() const;
virtual void dirty();
#ifdef SK_DUMP_ENABLED
diff --git a/src/animator/SkDisplayXMLParser.cpp b/src/animator/SkDisplayXMLParser.cpp
index 31be3da80d..515d33c71d 100644
--- a/src/animator/SkDisplayXMLParser.cpp
+++ b/src/animator/SkDisplayXMLParser.cpp
@@ -176,7 +176,7 @@ bool SkDisplayXMLParser::onEndElement(const char elem[])
return true;
if (parentIndex > 0) {
SkDisplayable* parent = fParents[parentIndex - 1].fDisplayable;
- bool result = parent->add(fMaker, displayable);
+ bool result = parent->addChild(fMaker, displayable);
if (fMaker.hasError())
return true;
if (result == false) {
diff --git a/src/animator/SkDisplayable.cpp b/src/animator/SkDisplayable.cpp
index 753764b3f5..ed2c626b7c 100644
--- a/src/animator/SkDisplayable.cpp
+++ b/src/animator/SkDisplayable.cpp
@@ -39,7 +39,7 @@ SkDisplayable::~SkDisplayable() {
#endif
}
-bool SkDisplayable::add(SkAnimateMaker& , SkDisplayable* child) {
+bool SkDisplayable::addChild(SkAnimateMaker& , SkDisplayable* child) {
return false;
}
diff --git a/src/animator/SkDisplayable.h b/src/animator/SkDisplayable.h
index 5fc2d3ef57..4fd47abc37 100644
--- a/src/animator/SkDisplayable.h
+++ b/src/animator/SkDisplayable.h
@@ -32,7 +32,7 @@ public:
SkDisplayable();
#endif
virtual ~SkDisplayable();
- virtual bool add(SkAnimateMaker& , SkDisplayable* child);
+ virtual bool addChild(SkAnimateMaker& , SkDisplayable* child);
virtual bool canContainDependents() const;
virtual bool childrenNeedDisposing() const;
virtual void clearBounder();
diff --git a/src/animator/SkDrawExtraPathEffect.cpp b/src/animator/SkDrawExtraPathEffect.cpp
index 65d9cba533..8cfde25804 100644
--- a/src/animator/SkDrawExtraPathEffect.cpp
+++ b/src/animator/SkDrawExtraPathEffect.cpp
@@ -22,7 +22,7 @@ class SkDrawShapePathEffect : public SkDrawPathEffect {
DECLARE_PRIVATE_MEMBER_INFO(DrawShapePathEffect);
SkDrawShapePathEffect();
virtual ~SkDrawShapePathEffect();
- virtual bool add(SkAnimateMaker& , SkDisplayable* ) SK_OVERRIDE;
+ virtual bool addChild(SkAnimateMaker& , SkDisplayable* ) SK_OVERRIDE;
virtual SkPathEffect* getPathEffect();
protected:
SkDrawable* addPath;
@@ -60,7 +60,7 @@ class SkDrawComposePathEffect : public SkDrawPathEffect {
DECLARE_EXTRAS_MEMBER_INFO(SkDrawComposePathEffect);
SkDrawComposePathEffect(SkDisplayTypes );
virtual ~SkDrawComposePathEffect();
- virtual bool add(SkAnimateMaker& , SkDisplayable* ) SK_OVERRIDE;
+ virtual bool addChild(SkAnimateMaker& , SkDisplayable* ) SK_OVERRIDE;
virtual SkPathEffect* getPathEffect();
virtual bool isPaint() const;
private:
@@ -94,8 +94,7 @@ public:
SK_DECLARE_UNFLATTENABLE_OBJECT()
protected:
- virtual SkScalar begin(SkScalar contourLength)
- {
+ virtual SkScalar begin(SkScalar contourLength) const {
SkScriptValue value;
SkAnimatorScript engine(*fMaker, NULL, SkType_Float);
engine.propertyCallBack(GetContourLength, &contourLength);
@@ -104,8 +103,7 @@ protected:
return value.fOperand.fScalar;
}
- virtual SkScalar next(SkPath* dst, SkScalar distance, SkPathMeasure& )
- {
+ virtual SkScalar next(SkPath* dst, SkScalar distance, SkPathMeasure&) const {
fMaker->setExtraPropertyCallBack(fDraw->fType, GetDistance, &distance);
SkDrawPath* drawPath = NULL;
if (fDraw->addPath->isPath()) {
@@ -184,7 +182,7 @@ SkDrawShapePathEffect::~SkDrawShapePathEffect() {
SkSafeUnref(fPathEffect);
}
-bool SkDrawShapePathEffect::add(SkAnimateMaker& , SkDisplayable* child) {
+bool SkDrawShapePathEffect::addChild(SkAnimateMaker& , SkDisplayable* child) {
path = (SkDrawPath*) child;
return true;
}
@@ -354,7 +352,7 @@ SkDrawComposePathEffect::~SkDrawComposePathEffect() {
delete effect2;
}
-bool SkDrawComposePathEffect::add(SkAnimateMaker& , SkDisplayable* child) {
+bool SkDrawComposePathEffect::addChild(SkAnimateMaker& , SkDisplayable* child) {
if (effect1 == NULL)
effect1 = (SkDrawPathEffect*) child;
else
diff --git a/src/animator/SkDrawGradient.cpp b/src/animator/SkDrawGradient.cpp
index 2e751cadf6..44086e4269 100644
--- a/src/animator/SkDrawGradient.cpp
+++ b/src/animator/SkDrawGradient.cpp
@@ -84,7 +84,7 @@ SkDrawGradient::~SkDrawGradient() {
delete fUnitMapper;
}
-bool SkDrawGradient::add(SkAnimateMaker& , SkDisplayable* child) {
+bool SkDrawGradient::addChild(SkAnimateMaker& , SkDisplayable* child) {
SkASSERT(child);
if (child->isColor()) {
SkDrawColor* color = (SkDrawColor*) child;
diff --git a/src/animator/SkDrawGradient.h b/src/animator/SkDrawGradient.h
index d7fc694c99..7763c1fcdc 100644
--- a/src/animator/SkDrawGradient.h
+++ b/src/animator/SkDrawGradient.h
@@ -20,7 +20,7 @@ class SkDrawGradient : public SkDrawShader {
DECLARE_PRIVATE_MEMBER_INFO(DrawGradient);
SkDrawGradient();
virtual ~SkDrawGradient();
- virtual bool add(SkAnimateMaker& , SkDisplayable* child) SK_OVERRIDE;
+ virtual bool addChild(SkAnimateMaker& , SkDisplayable* child) SK_OVERRIDE;
#ifdef SK_DUMP_ENABLED
virtual void dumpRest(SkAnimateMaker*);
#endif
diff --git a/src/animator/SkDrawGroup.cpp b/src/animator/SkDrawGroup.cpp
index 806da5eb07..ddd23d3526 100644
--- a/src/animator/SkDrawGroup.cpp
+++ b/src/animator/SkDrawGroup.cpp
@@ -49,7 +49,7 @@ SkGroup::~SkGroup() {
}
}
-bool SkGroup::add(SkAnimateMaker& , SkDisplayable* child) {
+bool SkGroup::addChild(SkAnimateMaker& , SkDisplayable* child) {
SkASSERT(child);
// SkASSERT(child->isDrawable());
*fChildren.append() = (SkDrawable*) child;
@@ -86,7 +86,7 @@ SkDisplayable* SkGroup::deepCopy(SkAnimateMaker* maker) {
for (SkDrawable** ptr = fChildren.begin(); ptr < fChildren.end(); ptr++) {
SkDisplayable* displayable = (SkDisplayable*)*ptr;
SkDisplayable* deeperCopy = displayable->deepCopy(maker);
- ((SkGroup*)copy)->add(*maker, deeperCopy);
+ ((SkGroup*)copy)->addChild(*maker, deeperCopy);
}
return copy;
}
diff --git a/src/animator/SkDrawGroup.h b/src/animator/SkDrawGroup.h
index a63a50eb83..336040c115 100644
--- a/src/animator/SkDrawGroup.h
+++ b/src/animator/SkDrawGroup.h
@@ -19,7 +19,7 @@ public:
DECLARE_MEMBER_INFO(Group);
SkGroup();
virtual ~SkGroup();
- virtual bool add(SkAnimateMaker& , SkDisplayable* child) SK_OVERRIDE;
+ virtual bool addChild(SkAnimateMaker& , SkDisplayable* child) SK_OVERRIDE;
virtual bool contains(SkDisplayable* );
SkGroup* copy();
SkBool copySet(int index);
diff --git a/src/animator/SkDrawMatrix.cpp b/src/animator/SkDrawMatrix.cpp
index 1d7b3e015e..62507ea43f 100644
--- a/src/animator/SkDrawMatrix.cpp
+++ b/src/animator/SkDrawMatrix.cpp
@@ -61,7 +61,7 @@ SkDrawMatrix::~SkDrawMatrix() {
delete *part;
}
-bool SkDrawMatrix::add(SkAnimateMaker& maker, SkDisplayable* child) {
+bool SkDrawMatrix::addChild(SkAnimateMaker& maker, SkDisplayable* child) {
SkASSERT(child && child->isMatrixPart());
SkMatrixPart* part = (SkMatrixPart*) child;
*fParts.append() = part;
diff --git a/src/animator/SkDrawMatrix.h b/src/animator/SkDrawMatrix.h
index cb781e71ad..e3c389a2cf 100644
--- a/src/animator/SkDrawMatrix.h
+++ b/src/animator/SkDrawMatrix.h
@@ -21,7 +21,7 @@ class SkDrawMatrix : public SkDrawable {
DECLARE_DRAW_MEMBER_INFO(Matrix);
SkDrawMatrix();
virtual ~SkDrawMatrix();
- virtual bool add(SkAnimateMaker& , SkDisplayable* child) SK_OVERRIDE;
+ virtual bool addChild(SkAnimateMaker& , SkDisplayable* child) SK_OVERRIDE;
virtual bool childrenNeedDisposing() const;
virtual void dirty();
virtual bool draw(SkAnimateMaker& );
diff --git a/src/animator/SkDrawPath.cpp b/src/animator/SkDrawPath.cpp
index 858db5438b..aaeeb0bd48 100644
--- a/src/animator/SkDrawPath.cpp
+++ b/src/animator/SkDrawPath.cpp
@@ -45,7 +45,7 @@ SkDrawPath::~SkDrawPath() {
delete *part;
}
-bool SkDrawPath::add(SkAnimateMaker& maker, SkDisplayable* child) {
+bool SkDrawPath::addChild(SkAnimateMaker& maker, SkDisplayable* child) {
SkASSERT(child && child->isPathPart());
SkPathPart* part = (SkPathPart*) child;
*fParts.append() = part;
@@ -188,7 +188,7 @@ const SkMemberInfo SkPolyline::fInfo[] = {
DEFINE_GET_MEMBER(SkPolyline);
-bool SkPolyline::add(SkAnimateMaker& , SkDisplayable*) {
+bool SkPolyline::addChild(SkAnimateMaker& , SkDisplayable*) {
return false;
}
diff --git a/src/animator/SkDrawPath.h b/src/animator/SkDrawPath.h
index 76e2e7e09a..9c4d3059d0 100644
--- a/src/animator/SkDrawPath.h
+++ b/src/animator/SkDrawPath.h
@@ -19,7 +19,7 @@ class SkDrawPath : public SkBoundable {
DECLARE_DRAW_MEMBER_INFO(Path);
SkDrawPath();
virtual ~SkDrawPath();
- virtual bool add(SkAnimateMaker& , SkDisplayable* child) SK_OVERRIDE;
+ virtual bool addChild(SkAnimateMaker& , SkDisplayable* child) SK_OVERRIDE;
bool childHasID() { return SkToBool(fChildHasID); }
virtual bool childrenNeedDisposing() const;
virtual void dirty();
@@ -51,7 +51,7 @@ private:
class SkPolyline : public SkDrawPath {
DECLARE_MEMBER_INFO(Polyline);
- virtual bool add(SkAnimateMaker& , SkDisplayable*) SK_OVERRIDE;
+ virtual bool addChild(SkAnimateMaker& , SkDisplayable*) SK_OVERRIDE;
virtual void onEndElement(SkAnimateMaker& );
protected:
SkTDScalarArray points;
diff --git a/src/core/SkAdvancedTypefaceMetrics.cpp b/src/core/SkAdvancedTypefaceMetrics.cpp
index 370616ef95..b647ada260 100644
--- a/src/core/SkAdvancedTypefaceMetrics.cpp
+++ b/src/core/SkAdvancedTypefaceMetrics.cpp
@@ -13,7 +13,7 @@
SK_DEFINE_INST_COUNT(SkAdvancedTypefaceMetrics)
#if defined(SK_BUILD_FOR_WIN)
-#include <DWrite.h>
+#include <dwrite.h>
#endif
#if defined(SK_BUILD_FOR_UNIX) || defined(SK_BUILD_FOR_ANDROID)
diff --git a/src/core/SkBBoxHierarchyRecord.cpp b/src/core/SkBBoxHierarchyRecord.cpp
index c27940af6f..16172f343d 100644
--- a/src/core/SkBBoxHierarchyRecord.cpp
+++ b/src/core/SkBBoxHierarchyRecord.cpp
@@ -97,3 +97,9 @@ bool SkBBoxHierarchyRecord::clipPath(const SkPath& path,
return INHERITED::clipPath(path, op, doAntiAlias);
}
+bool SkBBoxHierarchyRecord::clipRRect(const SkRRect& rrect,
+ SkRegion::Op op,
+ bool doAntiAlias) {
+ fStateTree->appendClip(this->writeStream().size());
+ return INHERITED::clipRRect(rrect, op, doAntiAlias);
+}
diff --git a/src/core/SkBBoxHierarchyRecord.h b/src/core/SkBBoxHierarchyRecord.h
index 4741338f2a..ac7ab1c1b4 100644
--- a/src/core/SkBBoxHierarchyRecord.h
+++ b/src/core/SkBBoxHierarchyRecord.h
@@ -43,6 +43,9 @@ public:
virtual bool clipPath(const SkPath& path,
SkRegion::Op op = SkRegion::kIntersect_Op,
bool doAntiAlias = false) SK_OVERRIDE;
+ virtual bool clipRRect(const SkRRect& rrect,
+ SkRegion::Op op = SkRegion::kIntersect_Op,
+ bool doAntiAlias = false) SK_OVERRIDE;
private:
typedef SkBBoxRecord INHERITED;
diff --git a/src/core/SkBBoxRecord.cpp b/src/core/SkBBoxRecord.cpp
index 5db77f5f40..1ca25616b1 100644
--- a/src/core/SkBBoxRecord.cpp
+++ b/src/core/SkBBoxRecord.cpp
@@ -8,6 +8,18 @@
#include "SkBBoxRecord.h"
+void SkBBoxRecord::drawOval(const SkRect& rect, const SkPaint& paint) {
+ if (this->transformBounds(rect, &paint)) {
+ INHERITED::drawOval(rect, paint);
+ }
+}
+
+void SkBBoxRecord::drawRRect(const SkRRect& rrect, const SkPaint& paint) {
+ if (this->transformBounds(rrect.rect(), &paint)) {
+ INHERITED::drawRRect(rrect, paint);
+ }
+}
+
void SkBBoxRecord::drawRect(const SkRect& rect, const SkPaint& paint) {
if (this->transformBounds(rect, &paint)) {
INHERITED::drawRect(rect, paint);
@@ -15,7 +27,15 @@ void SkBBoxRecord::drawRect(const SkRect& rect, const SkPaint& paint) {
}
void SkBBoxRecord::drawPath(const SkPath& path, const SkPaint& paint) {
- if (this->transformBounds(path.getBounds(), &paint)) {
+ if (path.isInverseFillType()) {
+ // If path is inverse filled, use the current clip bounds as the
+ // path's device-space bounding box.
+ SkIRect clipBounds;
+ if (this->getClipDeviceBounds(&clipBounds)) {
+ this->handleBBox(SkRect::MakeFromIRect(clipBounds));
+ INHERITED::drawPath(path, paint);
+ }
+ } else if (this->transformBounds(path.getBounds(), &paint)) {
INHERITED::drawPath(path, paint);
}
}
@@ -24,6 +44,18 @@ void SkBBoxRecord::drawPoints(PointMode mode, size_t count, const SkPoint pts[],
const SkPaint& paint) {
SkRect bbox;
bbox.set(pts, count);
+ // Small min width value, just to ensure hairline point bounding boxes aren't empty.
+ // Even though we know hairline primitives are drawn one pixel wide, we do not use a
+ // minimum of 1 because the playback scale factor is unknown at record time. Later
+ // outsets will take care of adding additional padding for antialiasing and rounding out
+ // to integer device coordinates, guaranteeing that the rasterized pixels will be included
+ // in the computed bounds.
+ // Note: The device coordinate outset in SkBBoxHierarchyRecord::handleBBox is currently
+ // done in the recording coordinate space, which is wrong.
+ // http://code.google.com/p/skia/issues/detail?id=1021
+ static const SkScalar kMinWidth = SkFloatToScalar(0.01f);
+ SkScalar halfStrokeWidth = SkMaxScalar(paint.getStrokeWidth(), kMinWidth) / 2;
+ bbox.outset(halfStrokeWidth, halfStrokeWidth);
if (this->transformBounds(bbox, &paint)) {
INHERITED::drawPoints(mode, count, pts, paint);
}
@@ -179,9 +211,10 @@ void SkBBoxRecord::drawPosTextH(const void* text, size_t byteLength, const SkSca
void SkBBoxRecord::drawSprite(const SkBitmap& bitmap, int left, int top,
const SkPaint* paint) {
- SkRect bbox = {SkIntToScalar(left), SkIntToScalar(top), SkIntToScalar(left + bitmap.width()), SkIntToScalar(top + bitmap.height())};
+ SkRect bbox;
+ bbox.set(SkIRect::MakeXYWH(left, top, bitmap.width(), bitmap.height()));
this->handleBBox(bbox); // directly call handleBBox, matrix is ignored
- INHERITED::drawBitmap(bitmap, left, top, paint);
+ INHERITED::drawSprite(bitmap, left, top, paint);
}
void SkBBoxRecord::drawTextOnPath(const void* text, size_t byteLength,
@@ -225,12 +258,13 @@ void SkBBoxRecord::drawPicture(SkPicture& picture) {
bool SkBBoxRecord::transformBounds(const SkRect& bounds, const SkPaint* paint) {
SkRect outBounds = bounds;
+ outBounds.sort();
if (paint) {
// account for stroking, path effects, shadows, etc
if (paint->canComputeFastBounds()) {
SkRect temp;
- outBounds = paint->computeFastBounds(bounds, &temp);
+ outBounds = paint->computeFastBounds(outBounds, &temp);
} else {
// set bounds to current clip
if (!this->getClipBounds(&outBounds)) {
diff --git a/src/core/SkBBoxRecord.h b/src/core/SkBBoxRecord.h
index e6b85db5f7..190d1450d1 100644
--- a/src/core/SkBBoxRecord.h
+++ b/src/core/SkBBoxRecord.h
@@ -29,6 +29,8 @@ public:
*/
virtual void handleBBox(const SkRect& bbox) = 0;
+ virtual void drawOval(const SkRect& rect, const SkPaint& paint) SK_OVERRIDE;
+ virtual void drawRRect(const SkRRect& rrect, const SkPaint& paint) SK_OVERRIDE;
virtual void drawRect(const SkRect& rect, const SkPaint& paint) SK_OVERRIDE;
virtual void drawPath(const SkPath& path, const SkPaint& paint) SK_OVERRIDE;
virtual void drawPoints(PointMode mode, size_t count, const SkPoint pts[],
diff --git a/src/core/SkBitmap.cpp b/src/core/SkBitmap.cpp
index 61a4c8f7a5..5554f46ce6 100644
--- a/src/core/SkBitmap.cpp
+++ b/src/core/SkBitmap.cpp
@@ -668,6 +668,81 @@ SkColor SkBitmap::getColor(int x, int y) const {
return 0;
}
+bool SkBitmap::ComputeIsOpaque(const SkBitmap& bm) {
+ SkAutoLockPixels alp(bm);
+ if (!bm.getPixels()) {
+ return false;
+ }
+
+ const int height = bm.height();
+ const int width = bm.width();
+
+ switch (bm.config()) {
+ case SkBitmap::kA1_Config: {
+ // TODO
+ } break;
+ case SkBitmap::kA8_Config: {
+ unsigned a = 0xFF;
+ for (int y = 0; y < height; ++y) {
+ const uint8_t* row = bm.getAddr8(0, y);
+ for (int x = 0; x < width; ++x) {
+ a &= row[x];
+ }
+ if (0xFF != a) {
+ return false;
+ }
+ }
+ return true;
+ } break;
+ case kRLE_Index8_Config:
+ case SkBitmap::kIndex8_Config: {
+ SkAutoLockColors alc(bm);
+ const SkPMColor* table = alc.colors();
+ if (!table) {
+ return false;
+ }
+ SkPMColor c = ~0;
+ for (int i = bm.getColorTable()->count() - 1; i >= 0; --i) {
+ c &= table[i];
+ }
+ return 0xFF == SkGetPackedA32(c);
+ } break;
+ case SkBitmap::kRGB_565_Config:
+ return true;
+ break;
+ case SkBitmap::kARGB_4444_Config: {
+ unsigned c = 0xFFFF;
+ for (int y = 0; y < height; ++y) {
+ const SkPMColor16* row = bm.getAddr16(0, y);
+ for (int x = 0; x < width; ++x) {
+ c &= row[x];
+ }
+ if (0xF != SkGetPackedA4444(c)) {
+ return false;
+ }
+ }
+ return true;
+ } break;
+ case SkBitmap::kARGB_8888_Config: {
+ SkPMColor c = ~0;
+ for (int y = 0; y < height; ++y) {
+ const SkPMColor* row = bm.getAddr32(0, y);
+ for (int x = 0; x < width; ++x) {
+ c &= row[x];
+ }
+ if (0xFF != SkGetPackedA32(c)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ default:
+ break;
+ }
+ return false;
+}
+
+
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
@@ -753,10 +828,18 @@ void SkBitmap::eraseARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b) const {
#define SUB_OFFSET_FAILURE ((size_t)-1)
-static size_t getSubOffset(const SkBitmap& bm, int x, int y) {
- SkASSERT((unsigned)x < (unsigned)bm.width());
- SkASSERT((unsigned)y < (unsigned)bm.height());
+// Declare these non-static so they can be tested by GpuBitmapCopyTest.
+size_t getSubOffset(const SkBitmap& bm, int x, int y);
+bool getUpperLeftFromOffset(const SkBitmap& bm, int* x, int* y);
+/**
+ * Based on the Config and rowBytes() of bm, return the offset into an SkPixelRef of the pixel at
+ * (x, y).
+ * Note that the SkPixelRef does not need to be set yet. deepCopyTo takes advantage of this fact.
+ * Also note that (x, y) may be outside the range of (0 - width(), 0 - height()), so long as it is
+ * within the bounds of the SkPixelRef being used.
+ */
+size_t getSubOffset(const SkBitmap& bm, int x, int y) {
switch (bm.getConfig()) {
case SkBitmap::kA8_Config:
case SkBitmap:: kIndex8_Config:
@@ -780,6 +863,49 @@ static size_t getSubOffset(const SkBitmap& bm, int x, int y) {
return y * bm.rowBytes() + x;
}
+/**
+ * Using the pixelRefOffset(), rowBytes(), and Config of bm, determine the (x, y) coordinate of the
+ * upper left corner of bm relative to its SkPixelRef.
+ * x and y must be non-NULL.
+ */
+bool getUpperLeftFromOffset(const SkBitmap& bm, int* x, int* y) {
+ SkASSERT(x != NULL && y != NULL);
+ const size_t offset = bm.pixelRefOffset();
+ if (0 == offset) {
+ *x = *y = 0;
+ return true;
+ }
+ // Use integer division to find the correct y position.
+ *y = offset / bm.rowBytes();
+ // The remainder will be the x position, after we reverse getSubOffset.
+ *x = offset % bm.rowBytes();
+ switch (bm.getConfig()) {
+ case SkBitmap::kA8_Config:
+ // Fall through.
+ case SkBitmap::kIndex8_Config:
+ // x is unmodified
+ break;
+
+ case SkBitmap::kRGB_565_Config:
+ // Fall through.
+ case SkBitmap::kARGB_4444_Config:
+ *x >>= 1;
+ break;
+
+ case SkBitmap::kARGB_8888_Config:
+ *x >>= 2;
+ break;
+
+ case SkBitmap::kNo_Config:
+ // Fall through.
+ case SkBitmap::kA1_Config:
+ // Fall through.
+ default:
+ return false;
+ }
+ return true;
+}
+
bool SkBitmap::extractSubset(SkBitmap* result, const SkIRect& subset) const {
SkDEBUGCODE(this->validate();)
@@ -793,6 +919,21 @@ bool SkBitmap::extractSubset(SkBitmap* result, const SkIRect& subset) const {
return false; // r is empty (i.e. no intersection)
}
+ if (fPixelRef->getTexture() != NULL) {
+ // Do a deep copy
+ SkPixelRef* pixelRef = fPixelRef->deepCopy(this->config(), &subset);
+ if (pixelRef != NULL) {
+ SkBitmap dst;
+ dst.setConfig(this->config(), subset.width(), subset.height());
+ dst.setIsVolatile(this->isVolatile());
+ dst.setIsOpaque(this->isOpaque());
+ dst.setPixelRef(pixelRef)->unref();
+ SkDEBUGCODE(dst.validate());
+ result->swap(dst);
+ return true;
+ }
+ }
+
if (kRLE_Index8_Config == fConfig) {
SkAutoLockPixels alp(*this);
// don't call readyToDraw(), since we can operate w/o a colortable
@@ -821,6 +962,11 @@ bool SkBitmap::extractSubset(SkBitmap* result, const SkIRect& subset) const {
return true;
}
+ // If the upper left of the rectangle was outside the bounds of this SkBitmap, we should have
+ // exited above.
+ SkASSERT(static_cast<unsigned>(r.fLeft) < static_cast<unsigned>(this->width()));
+ SkASSERT(static_cast<unsigned>(r.fTop) < static_cast<unsigned>(this->height()));
+
size_t offset = getSubOffset(*this, r.fLeft, r.fTop);
if (SUB_OFFSET_FAILURE == offset) {
return false; // config not supported
@@ -829,9 +975,7 @@ bool SkBitmap::extractSubset(SkBitmap* result, const SkIRect& subset) const {
SkBitmap dst;
dst.setConfig(this->config(), r.width(), r.height(), this->rowBytes());
dst.setIsVolatile(this->isVolatile());
-#ifndef SK_DISABLE_EXTRACTSUBSET_OPAQUE_FIX
dst.setIsOpaque(this->isOpaque());
-#endif
if (fPixelRef) {
// share the pixelref with a custom offset
@@ -888,21 +1032,28 @@ bool SkBitmap::copyTo(SkBitmap* dst, Config dstConfig, Allocator* alloc) const {
SkBitmap tmpSrc;
const SkBitmap* src = this;
- if (fPixelRef && fPixelRef->readPixels(&tmpSrc)) {
- SkASSERT(tmpSrc.width() == this->width());
- SkASSERT(tmpSrc.height() == this->height());
+ if (fPixelRef) {
+ SkIRect subset;
+ if (getUpperLeftFromOffset(*this, &subset.fLeft, &subset.fTop)) {
+ subset.fRight = subset.fLeft + fWidth;
+ subset.fBottom = subset.fTop + fHeight;
+ if (fPixelRef->readPixels(&tmpSrc, &subset)) {
+ SkASSERT(tmpSrc.width() == this->width());
+ SkASSERT(tmpSrc.height() == this->height());
+
+ // did we get lucky and we can just return tmpSrc?
+ if (tmpSrc.config() == dstConfig && NULL == alloc) {
+ dst->swap(tmpSrc);
+ if (dst->pixelRef() && this->config() == dstConfig) {
+ dst->pixelRef()->fGenerationID = fPixelRef->getGenerationID();
+ }
+ return true;
+ }
- // did we get lucky and we can just return tmpSrc?
- if (tmpSrc.config() == dstConfig && NULL == alloc) {
- dst->swap(tmpSrc);
- if (dst->pixelRef()) {
- dst->pixelRef()->fGenerationID = fPixelRef->getGenerationID();
+ // fall through to the raster case
+ src = &tmpSrc;
}
- return true;
}
-
- // fall through to the raster case
- src = &tmpSrc;
}
// we lock this now, since we may need its colortable
@@ -950,7 +1101,7 @@ bool SkBitmap::copyTo(SkBitmap* dst, Config dstConfig, Allocator* alloc) const {
} else {
// if the src has alpha, we have to clear the dst first
if (!src->isOpaque()) {
- tmpDst.eraseColor(0);
+ tmpDst.eraseColor(SK_ColorTRANSPARENT);
}
SkCanvas canvas(tmpDst);
@@ -976,11 +1127,34 @@ bool SkBitmap::deepCopyTo(SkBitmap* dst, Config dstConfig) const {
if (fPixelRef) {
SkPixelRef* pixelRef = fPixelRef->deepCopy(dstConfig);
if (pixelRef) {
+ uint32_t rowBytes;
if (dstConfig == fConfig) {
pixelRef->fGenerationID = fPixelRef->getGenerationID();
+ // Use the same rowBytes as the original.
+ rowBytes = fRowBytes;
+ } else {
+ // With the new config, an appropriate fRowBytes will be computed by setConfig.
+ rowBytes = 0;
+ }
+ dst->setConfig(dstConfig, fWidth, fHeight, rowBytes);
+
+ size_t pixelRefOffset;
+ if (0 == fPixelRefOffset || dstConfig == fConfig) {
+ // Use the same offset as the original.
+ pixelRefOffset = fPixelRefOffset;
+ } else {
+ // Find the correct offset in the new config. This needs to be done after calling
+ // setConfig so dst's fConfig and fRowBytes have been set properly.
+ int x, y;
+ if (!getUpperLeftFromOffset(*this, &x, &y)) {
+ return false;
+ }
+ pixelRefOffset = getSubOffset(*dst, x, y);
+ if (SUB_OFFSET_FAILURE == pixelRefOffset) {
+ return false;
+ }
}
- dst->setConfig(dstConfig, fWidth, fHeight);
- dst->setPixelRef(pixelRef)->unref();
+ dst->setPixelRef(pixelRef, pixelRefOffset)->unref();
return true;
}
}
diff --git a/src/core/SkBitmapHeap.cpp b/src/core/SkBitmapHeap.cpp
index 936951cc8e..48194b1eaa 100644
--- a/src/core/SkBitmapHeap.cpp
+++ b/src/core/SkBitmapHeap.cpp
@@ -367,13 +367,17 @@ int32_t SkBitmapHeap::insert(const SkBitmap& originalBitmap) {
// TODO if there is a shared pixel ref don't count it
// If the SkBitmap does not share an SkPixelRef with an SkBitmap already
// in the SharedHeap, also include the size of its pixels.
- entry->fBytesAllocated += originalBitmap.getSize();
+ entry->fBytesAllocated = originalBitmap.getSize();
// add the bytes from this entry to the total count
fBytesAllocated += entry->fBytesAllocated;
if (fOwnerCount != IGNORE_OWNERS) {
- entry->addReferences(fOwnerCount);
+ if (fDeferAddingOwners) {
+ *fDeferredEntries.append() = entry->fSlot;
+ } else {
+ entry->addReferences(fOwnerCount);
+ }
}
if (fPreferredCount != UNLIMITED_SIZE) {
this->appendToLRU(fLookupTable[searchIndex]);
diff --git a/src/core/SkBitmapHeap.h b/src/core/SkBitmapHeap.h
index badbc9cd84..be99e191a4 100644
--- a/src/core/SkBitmapHeap.h
+++ b/src/core/SkBitmapHeap.h
@@ -47,6 +47,7 @@ private:
size_t fBytesAllocated;
friend class SkBitmapHeap;
+ friend class SkBitmapHeapTester;
};
diff --git a/src/core/SkBitmapProcShader.cpp b/src/core/SkBitmapProcShader.cpp
index 66d9d7645d..3e4116651d 100644
--- a/src/core/SkBitmapProcShader.cpp
+++ b/src/core/SkBitmapProcShader.cpp
@@ -35,23 +35,12 @@ SkBitmapProcShader::SkBitmapProcShader(const SkBitmap& src,
SkBitmapProcShader::SkBitmapProcShader(SkFlattenableReadBuffer& buffer)
: INHERITED(buffer) {
buffer.readBitmap(&fRawBitmap);
+ fRawBitmap.setImmutable();
fState.fTileModeX = buffer.readUInt();
fState.fTileModeY = buffer.readUInt();
fFlags = 0; // computed in setContext
}
-void SkBitmapProcShader::beginSession() {
- this->INHERITED::beginSession();
-
- fRawBitmap.lockPixels();
-}
-
-void SkBitmapProcShader::endSession() {
- fRawBitmap.unlockPixels();
-
- this->INHERITED::endSession();
-}
-
SkShader::BitmapType SkBitmapProcShader::asABitmap(SkBitmap* texture,
SkMatrix* texM,
TileMode xy[]) const {
@@ -101,6 +90,7 @@ bool SkBitmapProcShader::setContext(const SkBitmap& device,
}
if (!fState.chooseProcs(this->getTotalInverse(), paint)) {
+ fState.fOrigBitmap.unlockPixels();
return false;
}
@@ -148,6 +138,11 @@ bool SkBitmapProcShader::setContext(const SkBitmap& device,
return true;
}
+void SkBitmapProcShader::endContext() {
+ fState.fOrigBitmap.unlockPixels();
+ this->INHERITED::endContext();
+}
+
#define BUF_MAX 128
#define TEST_BUFFER_OVERRITEx
@@ -329,10 +324,9 @@ bool SkBitmapProcShader::toDumpString(SkString* str) const {
// add the (optional) matrix
{
- SkMatrix m;
- if (this->getLocalMatrix(&m)) {
+ if (this->hasLocalMatrix()) {
SkString info;
- m.toDumpString(&info);
+ this->getLocalMatrix().toDumpString(&info);
str->appendf(" %s", info.c_str());
}
}
diff --git a/src/core/SkBitmapProcShader.h b/src/core/SkBitmapProcShader.h
index 23e099250b..cb791d0c92 100644
--- a/src/core/SkBitmapProcShader.h
+++ b/src/core/SkBitmapProcShader.h
@@ -20,12 +20,11 @@ public:
// overrides from SkShader
virtual bool isOpaque() const SK_OVERRIDE;
virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&);
+ virtual void endContext();
virtual uint32_t getFlags() { return fFlags; }
virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
virtual ShadeProc asAShadeProc(void** ctx) SK_OVERRIDE;
virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
- virtual void beginSession();
- virtual void endSession();
virtual BitmapType asABitmap(SkBitmap*, SkMatrix*, TileMode*) const;
static bool CanDo(const SkBitmap&, TileMode tx, TileMode ty);
diff --git a/src/core/SkBitmapProcState.cpp b/src/core/SkBitmapProcState.cpp
index 283a1ec171..ec2e0dd79a 100644
--- a/src/core/SkBitmapProcState.cpp
+++ b/src/core/SkBitmapProcState.cpp
@@ -32,6 +32,51 @@ extern void Clamp_SI8_opaque_D32_filter_DX_shaderproc_neon(const SkBitmapProcSt
///////////////////////////////////////////////////////////////////////////////
+/**
+ * For the purposes of drawing bitmaps, if a matrix is "almost" translate
+ * go ahead and treat it as if it were, so that subsequent code can go fast.
+ */
+static bool just_trans_clamp(const SkMatrix& matrix, const SkBitmap& bitmap) {
+ SkMatrix::TypeMask mask = matrix.getType();
+
+ if (mask & (SkMatrix::kAffine_Mask | SkMatrix::kPerspective_Mask)) {
+ return false;
+ }
+ if (mask & SkMatrix::kScale_Mask) {
+ SkScalar sx = matrix[SkMatrix::kMScaleX];
+ SkScalar sy = matrix[SkMatrix::kMScaleY];
+ int w = bitmap.width();
+ int h = bitmap.height();
+ int sw = SkScalarRound(SkScalarMul(sx, SkIntToScalar(w)));
+ int sh = SkScalarRound(SkScalarMul(sy, SkIntToScalar(h)));
+ return sw == w && sh == h;
+ }
+ // if we got here, we're either kTranslate_Mask or identity
+ return true;
+}
+
+static bool just_trans_general(const SkMatrix& matrix) {
+ SkMatrix::TypeMask mask = matrix.getType();
+
+ if (mask & (SkMatrix::kAffine_Mask | SkMatrix::kPerspective_Mask)) {
+ return false;
+ }
+ if (mask & SkMatrix::kScale_Mask) {
+ const SkScalar tol = SK_Scalar1 / 32768;
+
+ if (!SkScalarNearlyZero(matrix[SkMatrix::kMScaleX] - SK_Scalar1, tol)) {
+ return false;
+ }
+ if (!SkScalarNearlyZero(matrix[SkMatrix::kMScaleY] - SK_Scalar1, tol)) {
+ return false;
+ }
+ }
+ // if we got here, treat us as either kTranslate_Mask or identity
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
static bool valid_for_filtering(unsigned dimension) {
// for filtering, width and height must fit in 14bits, since we use steal
// 2 bits from each to store our 4bit subpixel data
@@ -46,7 +91,7 @@ bool SkBitmapProcState::chooseProcs(const SkMatrix& inv, const SkPaint& paint) {
const SkMatrix* m;
bool trivial_matrix = (inv.getType() & ~SkMatrix::kTranslate_Mask) == 0;
bool clamp_clamp = SkShader::kClamp_TileMode == fTileModeX &&
- SkShader::kClamp_TileMode == fTileModeY;
+ SkShader::kClamp_TileMode == fTileModeY;
if (clamp_clamp || trivial_matrix) {
m = &inv;
@@ -76,6 +121,23 @@ bool SkBitmapProcState::chooseProcs(const SkMatrix& inv, const SkPaint& paint) {
}
}
+ // wack our matrix to exactly no-scale, if we're really close to begin with
+ {
+ bool fixupMatrix = clamp_clamp ?
+ just_trans_clamp(*m, *fBitmap) : just_trans_general(*m);
+ if (fixupMatrix) {
+ if (m != &fUnitInvMatrix) { // can't mutate the original
+ fUnitInvMatrix = inv;
+ m = &fUnitInvMatrix;
+ }
+ fUnitInvMatrix.set(SkMatrix::kMScaleX, SK_Scalar1);
+ fUnitInvMatrix.set(SkMatrix::kMScaleY, SK_Scalar1);
+ }
+ }
+
+ // Below this point, we should never refer to the inv parameter, since we
+ // may be using a munged version for "our" inverse.
+
fInvMatrix = m;
fInvProc = m->getMapXYProc();
fInvType = m->getType();
@@ -92,7 +154,7 @@ bool SkBitmapProcState::chooseProcs(const SkMatrix& inv, const SkPaint& paint) {
// note: we explicitly check inv, since m might be scaled due to unitinv
// trickery, but we don't want to see that for this test
fDoFilter = paint.isFilterBitmap() &&
- (inv.getType() > SkMatrix::kTranslate_Mask &&
+ (fInvType > SkMatrix::kTranslate_Mask &&
valid_for_filtering(fBitmap->width() | fBitmap->height()));
fShaderProc32 = NULL;
@@ -290,6 +352,86 @@ static void Clamp_S32_D32_nofilter_trans_shaderproc(const SkBitmapProcState& s,
sk_memset32(colors, row[maxX], count);
}
+static inline int sk_int_mod(int x, int n) {
+ SkASSERT(n > 0);
+ if ((unsigned)x >= (unsigned)n) {
+ if (x < 0) {
+ x = n + ~(~x % n);
+ } else {
+ x = x % n;
+ }
+ }
+ return x;
+}
+
+static void Repeat_S32_D32_nofilter_trans_shaderproc(const SkBitmapProcState& s,
+ int x, int y,
+ SkPMColor* SK_RESTRICT colors,
+ int count) {
+ SkASSERT(((s.fInvType & ~SkMatrix::kTranslate_Mask)) == 0);
+ SkASSERT(s.fInvKy == 0);
+ SkASSERT(count > 0 && colors != NULL);
+ SkASSERT(!s.fDoFilter);
+
+ const int stopX = s.fBitmap->width();
+ const int stopY = s.fBitmap->height();
+ int ix = s.fFilterOneX + x;
+ int iy = sk_int_mod(s.fFilterOneY + y, stopY);
+#ifdef SK_DEBUG
+ {
+ SkPoint pt;
+ s.fInvProc(*s.fInvMatrix, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &pt);
+ int iy2 = sk_int_mod(SkScalarFloorToInt(pt.fY), stopY);
+ int ix2 = SkScalarFloorToInt(pt.fX);
+
+ SkASSERT(iy == iy2);
+ SkASSERT(ix == ix2);
+ }
+#endif
+ const SkPMColor* row = s.fBitmap->getAddr32(0, iy);
+
+ ix = sk_int_mod(ix, stopX);
+ for (;;) {
+ int n = SkMin32(stopX - ix, count);
+ memcpy(colors, row + ix, n * sizeof(SkPMColor));
+ count -= n;
+ if (0 == count) {
+ return;
+ }
+ colors += n;
+ ix = 0;
+ }
+}
+
+static void DoNothing_shaderproc(const SkBitmapProcState&, int x, int y,
+ SkPMColor* SK_RESTRICT colors, int count) {
+ // if we get called, the matrix is too tricky, so we just draw nothing
+ sk_memset32(colors, 0, count);
+}
+
+bool SkBitmapProcState::setupForTranslate() {
+ SkPoint pt;
+ fInvProc(*fInvMatrix, SK_ScalarHalf, SK_ScalarHalf, &pt);
+
+ /*
+ * if the translate is larger than our ints, we can get random results, or
+ * worse, we might get 0x80000000, which wreaks havoc on us, since we can't
+ * negate it.
+ */
+ const SkScalar too_big = SkIntToScalar(1 << 30);
+ if (SkScalarAbs(pt.fX) > too_big || SkScalarAbs(pt.fY) > too_big) {
+ return false;
+ }
+
+ // Since we know we're not filtered, we re-purpose these fields allow
+ // us to go from device -> src coordinates w/ just an integer add,
+ // rather than running through the inverse-matrix
+ fFilterOneX = SkScalarFloorToInt(pt.fX);
+ fFilterOneY = SkScalarFloorToInt(pt.fY);
+ return true;
+}
+
SkBitmapProcState::ShaderProc32 SkBitmapProcState::chooseShaderProc32() {
if (fAlphaScale < 256) {
return NULL;
@@ -304,15 +446,20 @@ SkBitmapProcState::ShaderProc32 SkBitmapProcState::chooseShaderProc32() {
return NULL;
}
- if (SkShader::kClamp_TileMode == fTileModeX && SkShader::kClamp_TileMode == fTileModeY) {
- SkPoint pt;
- fInvProc(*fInvMatrix, SK_ScalarHalf, SK_ScalarHalf, &pt);
- // Since we know we're not filtered, we re-purpose these fields allow
- // us to go from device -> src coordinates w/ just an integer add,
- // rather than running through the inverse-matrix
- fFilterOneX = SkScalarFloorToInt(pt.fX);
- fFilterOneY = SkScalarFloorToInt(pt.fY);
- return Clamp_S32_D32_nofilter_trans_shaderproc;
+ SkShader::TileMode tx = (SkShader::TileMode)fTileModeX;
+ SkShader::TileMode ty = (SkShader::TileMode)fTileModeY;
+
+ if (SkShader::kClamp_TileMode == tx && SkShader::kClamp_TileMode == ty) {
+ if (this->setupForTranslate()) {
+ return Clamp_S32_D32_nofilter_trans_shaderproc;
+ }
+ return DoNothing_shaderproc;
+ }
+ if (SkShader::kRepeat_TileMode == tx && SkShader::kRepeat_TileMode == ty) {
+ if (this->setupForTranslate()) {
+ return Repeat_S32_D32_nofilter_trans_shaderproc;
+ }
+ return DoNothing_shaderproc;
}
return NULL;
}
diff --git a/src/core/SkBitmapProcState.h b/src/core/SkBitmapProcState.h
index d9ac5c717b..69f4c2f0d2 100644
--- a/src/core/SkBitmapProcState.h
+++ b/src/core/SkBitmapProcState.h
@@ -139,6 +139,9 @@ private:
bool chooseProcs(const SkMatrix& inv, const SkPaint&);
ShaderProc32 chooseShaderProc32();
+ // Return false if we failed to setup for fast translate (e.g. overflow)
+ bool setupForTranslate();
+
#ifdef SK_DEBUG
static void DebugMatrixProc(const SkBitmapProcState&,
uint32_t[], int count, int x, int y);
diff --git a/src/core/SkBlitter.cpp b/src/core/SkBlitter.cpp
index cdb2b625a7..efe9d11a9d 100644
--- a/src/core/SkBlitter.cpp
+++ b/src/core/SkBlitter.cpp
@@ -22,6 +22,8 @@
SkBlitter::~SkBlitter() {}
+bool SkBlitter::isNullBlitter() const { return false; }
+
const SkBitmap* SkBlitter::justAnOpaqueColor(uint32_t* value) {
return NULL;
}
@@ -236,6 +238,8 @@ const SkBitmap* SkNullBlitter::justAnOpaqueColor(uint32_t* value) {
return NULL;
}
+bool SkNullBlitter::isNullBlitter() const { return true; }
+
///////////////////////////////////////////////////////////////////////////////
static int compute_anti_width(const int16_t runs[]) {
@@ -572,16 +576,30 @@ public:
void setMask(const SkMask* mask) { fMask = mask; }
virtual bool setContext(const SkBitmap& device, const SkPaint& paint,
- const SkMatrix& matrix) {
+ const SkMatrix& matrix) SK_OVERRIDE {
+ if (!this->INHERITED::setContext(device, paint, matrix)) {
+ return false;
+ }
if (fProxy) {
- return fProxy->setContext(device, paint, matrix);
+ if (!fProxy->setContext(device, paint, matrix)) {
+ // must keep our set/end context calls balanced
+ this->INHERITED::endContext();
+ return false;
+ }
} else {
fPMColor = SkPreMultiplyColor(paint.getColor());
- return this->INHERITED::setContext(device, paint, matrix);
}
+ return true;
+ }
+
+ virtual void endContext() SK_OVERRIDE {
+ if (fProxy) {
+ fProxy->endContext();
+ }
+ this->INHERITED::endContext();
}
- virtual void shadeSpan(int x, int y, SkPMColor span[], int count) {
+ virtual void shadeSpan(int x, int y, SkPMColor span[], int count) SK_OVERRIDE {
if (fProxy) {
fProxy->shadeSpan(x, y, span, count);
}
@@ -645,20 +663,6 @@ public:
}
}
- virtual void beginSession() {
- this->INHERITED::beginSession();
- if (fProxy) {
- fProxy->beginSession();
- }
- }
-
- virtual void endSession() {
- if (fProxy) {
- fProxy->endSession();
- }
- this->INHERITED::endSession();
- }
-
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Sk3DShader)
protected:
@@ -871,12 +875,21 @@ SkBlitter* SkBlitter::Choose(const SkBitmap& device,
}
}
+ /*
+ * If the xfermode is CLEAR, then we can completely ignore the installed
+ * color/shader/colorfilter, and just pretend we're SRC + color==0. This
+ * will fall into our optimizations for SRC mode.
+ */
+ if (SkXfermode::IsMode(mode, SkXfermode::kClear_Mode)) {
+ SkPaint* p = paint.writable();
+ shader = p->setShader(NULL);
+ cf = p->setColorFilter(NULL);
+ mode = p->setXfermodeMode(SkXfermode::kSrc_Mode);
+ p->setColor(0);
+ }
+
if (NULL == shader) {
-#ifdef SK_IGNORE_CF_OPTIMIZATION
- if (mode || cf) {
-#else
if (mode) {
-#endif
// xfermodes (and filters) require shaders for our current blitters
shader = SkNEW(SkColorShader);
paint.writable()->setShader(shader)->unref();
@@ -898,8 +911,17 @@ SkBlitter* SkBlitter::Choose(const SkBitmap& device,
// if there is one, the shader will take care of it.
}
+ /*
+ * We need to have balanced calls to the shader:
+ * setContext
+ * endContext
+ * We make the first call here, in case it fails we can abort the draw.
+ * The endContext() call is made by the blitter (assuming setContext did
+ * not fail) in its destructor.
+ */
if (shader && !shader->setContext(device, *paint, matrix)) {
- return SkNEW(SkNullBlitter);
+ SK_PLACEMENT_NEW(blitter, SkNullBlitter, storage, storageSize);
+ return blitter;
}
switch (device.getConfig()) {
@@ -969,14 +991,15 @@ SkShaderBlitter::SkShaderBlitter(const SkBitmap& device, const SkPaint& paint)
: INHERITED(device) {
fShader = paint.getShader();
SkASSERT(fShader);
+ SkASSERT(fShader->setContextHasBeenCalled());
fShader->ref();
- fShader->beginSession();
fShaderFlags = fShader->getFlags();
}
SkShaderBlitter::~SkShaderBlitter() {
- fShader->endSession();
+ SkASSERT(fShader->setContextHasBeenCalled());
+ fShader->endContext();
fShader->unref();
}
diff --git a/src/core/SkBlitter.h b/src/core/SkBlitter.h
index ce74a28d0e..0a075f89ad 100644
--- a/src/core/SkBlitter.h
+++ b/src/core/SkBlitter.h
@@ -51,6 +51,13 @@ public:
*/
virtual const SkBitmap* justAnOpaqueColor(uint32_t* value);
+ /**
+ * Special method just to identify the null blitter, which is returned
+ * from Choose() if the request cannot be fulfilled. Default impl
+ * returns false.
+ */
+ virtual bool isNullBlitter() const;
+
///@name non-virtual helpers
void blitMaskRegion(const SkMask& mask, const SkRegion& clip);
void blitRectRegion(const SkIRect& rect, const SkRegion& clip);
@@ -92,6 +99,7 @@ public:
virtual void blitRect(int x, int y, int width, int height) SK_OVERRIDE;
virtual void blitMask(const SkMask&, const SkIRect& clip) SK_OVERRIDE;
virtual const SkBitmap* justAnOpaqueColor(uint32_t* value) SK_OVERRIDE;
+ virtual bool isNullBlitter() const SK_OVERRIDE;
};
/** Wraps another (real) blitter, and ensures that the real blitter is only
diff --git a/src/core/SkBlitter_ARGB32.cpp b/src/core/SkBlitter_ARGB32.cpp
index f944f44dc1..8b47481b3c 100644
--- a/src/core/SkBlitter_ARGB32.cpp
+++ b/src/core/SkBlitter_ARGB32.cpp
@@ -263,6 +263,17 @@ void SkARGB32_Black_Blitter::blitAntiH(int x, int y, const SkAlpha antialias[],
///////////////////////////////////////////////////////////////////////////////
+// Special version of SkBlitRow::Factory32 that knows we're in kSrc_Mode,
+// instead of kSrcOver_Mode
+static void blend_srcmode(SkPMColor* SK_RESTRICT device,
+ const SkPMColor* SK_RESTRICT span,
+ int count, U8CPU aa) {
+ int aa256 = SkAlpha255To256(aa);
+ for (int i = 0; i < count; ++i) {
+ device[i] = SkFourByteInterp256(span[i], device[i], aa256);
+ }
+}
+
SkARGB32_Shader_Blitter::SkARGB32_Shader_Blitter(const SkBitmap& device,
const SkPaint& paint) : INHERITED(device, paint) {
fBuffer = (SkPMColor*)sk_malloc_throw(device.width() * (sizeof(SkPMColor)));
@@ -278,6 +289,23 @@ SkARGB32_Shader_Blitter::SkARGB32_Shader_Blitter(const SkBitmap& device,
fProc32 = SkBlitRow::Factory32(flags);
// we call this on the output from the shader + alpha from the aa buffer
fProc32Blend = SkBlitRow::Factory32(flags | SkBlitRow::kGlobalAlpha_Flag32);
+
+ fShadeDirectlyIntoDevice = false;
+ if (fXfermode == NULL) {
+ if (fShader->getFlags() & SkShader::kOpaqueAlpha_Flag) {
+ fShadeDirectlyIntoDevice = true;
+ }
+ } else {
+ SkXfermode::Mode mode;
+ if (fXfermode->asMode(&mode)) {
+ if (SkXfermode::kSrc_Mode == mode) {
+ fShadeDirectlyIntoDevice = true;
+ fProc32Blend = blend_srcmode;
+ }
+ }
+ }
+
+ fConstInY = SkToBool(fShader->getFlags() & SkShader::kConstInY32_Flag);
}
SkARGB32_Shader_Blitter::~SkARGB32_Shader_Blitter() {
@@ -290,7 +318,7 @@ void SkARGB32_Shader_Blitter::blitH(int x, int y, int width) {
uint32_t* device = fDevice.getAddr32(x, y);
- if (fXfermode == NULL && (fShader->getFlags() & SkShader::kOpaqueAlpha_Flag)) {
+ if (fShadeDirectlyIntoDevice) {
fShader->shadeSpan(x, y, device, width);
} else {
SkPMColor* span = fBuffer;
@@ -310,8 +338,39 @@ void SkARGB32_Shader_Blitter::blitRect(int x, int y, int width, int height) {
uint32_t* device = fDevice.getAddr32(x, y);
size_t deviceRB = fDevice.rowBytes();
SkShader* shader = fShader;
+ SkPMColor* span = fBuffer;
+
+ if (fConstInY) {
+ if (fShadeDirectlyIntoDevice) {
+ // shade the first row directly into the device
+ fShader->shadeSpan(x, y, device, width);
+ span = device;
+ while (--height > 0) {
+ device = (uint32_t*)((char*)device + deviceRB);
+ memcpy(device, span, width << 2);
+ }
+ } else {
+ fShader->shadeSpan(x, y, span, width);
+ SkXfermode* xfer = fXfermode;
+ if (xfer) {
+ do {
+ xfer->xfer32(device, span, width, NULL);
+ y += 1;
+ device = (uint32_t*)((char*)device + deviceRB);
+ } while (--height > 0);
+ } else {
+ SkBlitRow::Proc32 proc = fProc32;
+ do {
+ proc(device, span, width, 255);
+ y += 1;
+ device = (uint32_t*)((char*)device + deviceRB);
+ } while (--height > 0);
+ }
+ }
+ return;
+ }
- if (fXfermode == NULL && (shader->getFlags() & SkShader::kOpaqueAlpha_Flag)) {
+ if (fShadeDirectlyIntoDevice) {
void* ctx;
SkShader::ShadeProc shadeProc = fShader->asAShadeProc(&ctx);
if (shadeProc) {
@@ -328,7 +387,6 @@ void SkARGB32_Shader_Blitter::blitRect(int x, int y, int width, int height) {
} while (--height > 0);
}
} else {
- SkPMColor* span = fBuffer;
SkXfermode* xfer = fXfermode;
if (xfer) {
do {
@@ -355,7 +413,7 @@ void SkARGB32_Shader_Blitter::blitAntiH(int x, int y, const SkAlpha antialias[],
uint32_t* device = fDevice.getAddr32(x, y);
SkShader* shader = fShader;
- if (fXfermode) {
+ if (fXfermode && !fShadeDirectlyIntoDevice) {
for (;;) {
SkXfermode* xfer = fXfermode;
@@ -379,7 +437,8 @@ void SkARGB32_Shader_Blitter::blitAntiH(int x, int y, const SkAlpha antialias[],
antialias += count;
x += count;
}
- } else if (fShader->getFlags() & SkShader::kOpaqueAlpha_Flag) {
+ } else if (fShadeDirectlyIntoDevice ||
+ (fShader->getFlags() & SkShader::kOpaqueAlpha_Flag)) {
for (;;) {
int count = *runs;
if (count <= 0) {
@@ -400,7 +459,7 @@ void SkARGB32_Shader_Blitter::blitAntiH(int x, int y, const SkAlpha antialias[],
antialias += count;
x += count;
}
- } else { // no xfermode but the shader not opaque
+ } else {
for (;;) {
int count = *runs;
if (count <= 0) {
@@ -480,3 +539,101 @@ void SkARGB32_Shader_Blitter::blitMask(const SkMask& mask, const SkIRect& clip)
}
}
+void SkARGB32_Shader_Blitter::blitV(int x, int y, int height, SkAlpha alpha) {
+ SkASSERT(x >= 0 && y >= 0 && y + height <= fDevice.height());
+
+ uint32_t* device = fDevice.getAddr32(x, y);
+ size_t deviceRB = fDevice.rowBytes();
+ SkShader* shader = fShader;
+
+ if (fConstInY) {
+ SkPMColor c;
+ fShader->shadeSpan(x, y, &c, 1);
+
+ if (fShadeDirectlyIntoDevice) {
+ if (255 == alpha) {
+ do {
+ *device = c;
+ device = (uint32_t*)((char*)device + deviceRB);
+ } while (--height > 0);
+ } else {
+ do {
+ *device = SkFourByteInterp(c, *device, alpha);
+ device = (uint32_t*)((char*)device + deviceRB);
+ } while (--height > 0);
+ }
+ } else {
+ SkXfermode* xfer = fXfermode;
+ if (xfer) {
+ do {
+ xfer->xfer32(device, &c, 1, &alpha);
+ device = (uint32_t*)((char*)device + deviceRB);
+ } while (--height > 0);
+ } else {
+ SkBlitRow::Proc32 proc = (255 == alpha) ? fProc32 : fProc32Blend;
+ do {
+ proc(device, &c, 1, alpha);
+ device = (uint32_t*)((char*)device + deviceRB);
+ } while (--height > 0);
+ }
+ }
+ return;
+ }
+
+ if (fShadeDirectlyIntoDevice) {
+ void* ctx;
+ SkShader::ShadeProc shadeProc = fShader->asAShadeProc(&ctx);
+ if (255 == alpha) {
+ if (shadeProc) {
+ do {
+ shadeProc(ctx, x, y, device, 1);
+ y += 1;
+ device = (uint32_t*)((char*)device + deviceRB);
+ } while (--height > 0);
+ } else {
+ do {
+ shader->shadeSpan(x, y, device, 1);
+ y += 1;
+ device = (uint32_t*)((char*)device + deviceRB);
+ } while (--height > 0);
+ }
+ } else { // alpha < 255
+ SkPMColor c;
+ if (shadeProc) {
+ do {
+ shadeProc(ctx, x, y, &c, 1);
+ *device = SkFourByteInterp(c, *device, alpha);
+ y += 1;
+ device = (uint32_t*)((char*)device + deviceRB);
+ } while (--height > 0);
+ } else {
+ do {
+ shader->shadeSpan(x, y, &c, 1);
+ *device = SkFourByteInterp(c, *device, alpha);
+ y += 1;
+ device = (uint32_t*)((char*)device + deviceRB);
+ } while (--height > 0);
+ }
+ }
+ } else {
+ SkPMColor* span = fBuffer;
+ SkXfermode* xfer = fXfermode;
+ if (xfer) {
+ do {
+ shader->shadeSpan(x, y, span, 1);
+ xfer->xfer32(device, span, 1, &alpha);
+ y += 1;
+ device = (uint32_t*)((char*)device + deviceRB);
+ } while (--height > 0);
+ } else {
+ SkBlitRow::Proc32 proc = (255 == alpha) ? fProc32 : fProc32Blend;
+ do {
+ shader->shadeSpan(x, y, span, 1);
+ proc(device, span, 1, alpha);
+ y += 1;
+ device = (uint32_t*)((char*)device + deviceRB);
+ } while (--height > 0);
+ }
+ }
+}
+
diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp
index 8fb0bd3d7e..096b42dc79 100644
--- a/src/core/SkCanvas.cpp
+++ b/src/core/SkCanvas.cpp
@@ -17,6 +17,7 @@
#include "SkMetaData.h"
#include "SkPicture.h"
#include "SkRasterClip.h"
+#include "SkRRect.h"
#include "SkScalarCompare.h"
#include "SkSurface_Base.h"
#include "SkTemplates.h"
@@ -54,6 +55,52 @@ SK_DEFINE_INST_COUNT(SkDrawFilter)
#define dec_canvas()
#endif
+#ifdef SK_DEBUG
+#include "SkPixelRef.h"
+
+class AutoCheckLockCountBalance {
+public:
+ AutoCheckLockCountBalance(const SkBitmap& bm) : fPixelRef(bm.pixelRef()) {
+ fLockCount = fPixelRef ? fPixelRef->getLockCount() : 0;
+ }
+ ~AutoCheckLockCountBalance() {
+ const int count = fPixelRef ? fPixelRef->getLockCount() : 0;
+ SkASSERT(count == fLockCount);
+ }
+
+private:
+ const SkPixelRef* fPixelRef;
+ int fLockCount;
+};
+
+class AutoCheckNoSetContext {
+public:
+ AutoCheckNoSetContext(const SkPaint& paint) : fPaint(paint) {
+ this->assertNoSetContext(fPaint);
+ }
+ ~AutoCheckNoSetContext() {
+ this->assertNoSetContext(fPaint);
+ }
+
+private:
+ const SkPaint& fPaint;
+
+ void assertNoSetContext(const SkPaint& paint) {
+ SkShader* s = paint.getShader();
+ if (s) {
+ SkASSERT(!s->setContextHasBeenCalled());
+ }
+ }
+};
+
+#define CHECK_LOCKCOUNT_BALANCE(bitmap) AutoCheckLockCountBalance clcb(bitmap)
+#define CHECK_SHADER_NOSETCONTEXT(paint) AutoCheckNoSetContext cshsc(paint)
+
+#else
+ #define CHECK_LOCKCOUNT_BALANCE(bitmap)
+ #define CHECK_SHADER_NOSETCONTEXT(paint)
+#endif
+
typedef SkTLazy<SkPaint> SkLazyPaint;
void SkCanvas::predrawNotify() {
@@ -354,7 +401,10 @@ bool AutoDrawLooper::doNext(SkDrawFilter::Type drawType) {
return false;
}
if (fFilter) {
- fFilter->filter(paint, drawType);
+ if (!fFilter->filter(paint, drawType)) {
+ fDone = true;
+ return false;
+ }
if (NULL == fLooper) {
// no looper means we only draw once
fDone = true;
@@ -440,6 +490,7 @@ SkDevice* SkCanvas::init(SkDevice* device) {
fBounder = NULL;
fLocalBoundsCompareType.setEmpty();
fLocalBoundsCompareTypeDirty = true;
+ fAllowSoftClip = true;
fDeviceCMDirty = false;
fSaveLayerCount = 0;
fMetaData = NULL;
@@ -593,12 +644,6 @@ SkDevice* SkCanvas::setDevice(SkDevice* device) {
return device;
}
-SkDevice* SkCanvas::setBitmapDevice(const SkBitmap& bitmap) {
- SkDevice* device = this->setDevice(SkNEW_ARGS(SkDevice, (bitmap)));
- device->unref();
- return device;
-}
-
bool SkCanvas::readPixels(SkBitmap* bitmap,
int x, int y,
Config8888 config8888) {
@@ -964,6 +1009,7 @@ void SkCanvas::internalDrawDevice(SkDevice* srcDev, int x, int y,
void SkCanvas::drawSprite(const SkBitmap& bitmap, int x, int y,
const SkPaint* paint) {
SkDEBUGCODE(bitmap.validate();)
+ CHECK_LOCKCOUNT_BALANCE(bitmap);
if (reject_bitmap(bitmap)) {
return;
@@ -1067,6 +1113,7 @@ bool SkCanvas::clipRect(const SkRect& rect, SkRegion::Op op, bool doAA) {
fDeviceCMDirty = true;
fLocalBoundsCompareTypeDirty = true;
+ doAA &= fAllowSoftClip;
if (fMCRec->fMatrix->rectStaysRect()) {
// for these simpler matrices, we can stay a rect ever after applying
@@ -1126,6 +1173,18 @@ static bool clipPathHelper(const SkCanvas* canvas, SkRasterClip* currClip,
}
}
+bool SkCanvas::clipRRect(const SkRRect& rrect, SkRegion::Op op, bool doAA) {
+ if (rrect.isRect()) {
+ // call the non-virtual version
+ return this->SkCanvas::clipRect(rrect.getBounds(), op, doAA);
+ } else {
+ SkPath path;
+ path.addRRect(rrect);
+ // call the non-virtual version
+ return this->SkCanvas::clipPath(path, op, doAA);
+ }
+}
+
bool SkCanvas::clipPath(const SkPath& path, SkRegion::Op op, bool doAA) {
#ifdef SK_ENABLE_CLIP_QUICKREJECT
if (SkRegion::kIntersect_Op == op && !path.isInverseFillType()) {
@@ -1147,6 +1206,7 @@ bool SkCanvas::clipPath(const SkPath& path, SkRegion::Op op, bool doAA) {
fDeviceCMDirty = true;
fLocalBoundsCompareTypeDirty = true;
+ doAA &= fAllowSoftClip;
SkPath devPath;
path.transform(*fMCRec->fMatrix, &devPath);
@@ -1194,15 +1254,23 @@ void SkCanvas::validateClip() const {
SkRasterClip tmpClip(ir);
SkClipStack::B2TIter iter(fClipStack);
- const SkClipStack::B2TIter::Clip* clip;
- while ((clip = iter.next()) != NULL) {
- if (clip->fPath) {
- clipPathHelper(this, &tmpClip, *clip->fPath, clip->fOp, clip->fDoAA);
- } else if (clip->fRect) {
- clip->fRect->round(&ir);
- tmpClip.op(ir, clip->fOp);
- } else {
- tmpClip.setEmpty();
+ const SkClipStack::Element* element;
+ while ((element = iter.next()) != NULL) {
+ switch (element->getType()) {
+ case SkClipStack::Element::kPath_Type:
+ clipPathHelper(this,
+ &tmpClip,
+ element->getPath(),
+ element->getOp(),
+ element->isAA());
+ break;
+ case SkClipStack::Element::kRect_Type:
+ element->getRect().round(&ir);
+ tmpClip.op(ir, element->getOp());
+ break;
+ case SkClipStack::Element::kEmpty_Type:
+ tmpClip.setEmpty();
+ break;
}
}
@@ -1216,16 +1284,20 @@ void SkCanvas::validateClip() const {
void SkCanvas::replayClips(ClipVisitor* visitor) const {
SkClipStack::B2TIter iter(fClipStack);
- const SkClipStack::B2TIter::Clip* clip;
-
- SkRect empty = { 0, 0, 0, 0 };
- while ((clip = iter.next()) != NULL) {
- if (clip->fPath) {
- visitor->clipPath(*clip->fPath, clip->fOp, clip->fDoAA);
- } else if (clip->fRect) {
- visitor->clipRect(*clip->fRect, clip->fOp, clip->fDoAA);
- } else {
- visitor->clipRect(empty, SkRegion::kIntersect_Op, false);
+ const SkClipStack::Element* element;
+
+ static const SkRect kEmpty = { 0, 0, 0, 0 };
+ while ((element = iter.next()) != NULL) {
+ switch (element->getType()) {
+ case SkClipStack::Element::kPath_Type:
+ visitor->clipPath(element->getPath(), element->getOp(), element->isAA());
+ break;
+ case SkClipStack::Element::kRect_Type:
+ visitor->clipRect(element->getRect(), element->getOp(), element->isAA());
+ break;
+ case SkClipStack::Element::kEmpty_Type:
+ visitor->clipRect(kEmpty, SkRegion::kIntersect_Op, false);
+ break;
}
}
}
@@ -1397,6 +1469,8 @@ void SkCanvas::drawPaint(const SkPaint& paint) {
}
void SkCanvas::internalDrawPaint(const SkPaint& paint) {
+ CHECK_SHADER_NOSETCONTEXT(paint);
+
LOOPER_BEGIN(paint, SkDrawFilter::kPaint_Type)
while (iter.next()) {
@@ -1412,6 +1486,8 @@ void SkCanvas::drawPoints(PointMode mode, size_t count, const SkPoint pts[],
return;
}
+ CHECK_SHADER_NOSETCONTEXT(paint);
+
if (paint.canComputeFastBounds()) {
SkRect r;
// special-case 2 points (common for drawing a single line)
@@ -1438,6 +1514,8 @@ void SkCanvas::drawPoints(PointMode mode, size_t count, const SkPoint pts[],
}
void SkCanvas::drawRect(const SkRect& r, const SkPaint& paint) {
+ CHECK_SHADER_NOSETCONTEXT(paint);
+
if (paint.canComputeFastBounds()) {
SkRect storage;
if (this->quickReject(paint.computeFastBounds(r, &storage))) {
@@ -1454,7 +1532,47 @@ void SkCanvas::drawRect(const SkRect& r, const SkPaint& paint) {
LOOPER_END
}
+void SkCanvas::drawOval(const SkRect& oval, const SkPaint& paint) {
+ CHECK_SHADER_NOSETCONTEXT(paint);
+
+ if (paint.canComputeFastBounds()) {
+ SkRect storage;
+ if (this->quickReject(paint.computeFastBounds(oval, &storage))) {
+ return;
+ }
+ }
+
+ SkPath path;
+ path.addOval(oval);
+ // call the non-virtual version
+ this->SkCanvas::drawPath(path, paint);
+}
+
+void SkCanvas::drawRRect(const SkRRect& rrect, const SkPaint& paint) {
+ CHECK_SHADER_NOSETCONTEXT(paint);
+
+ if (paint.canComputeFastBounds()) {
+ SkRect storage;
+ if (this->quickReject(paint.computeFastBounds(rrect.getBounds(), &storage))) {
+ return;
+ }
+ }
+
+ if (rrect.isRect()) {
+ // call the non-virtual version
+ this->SkCanvas::drawRect(rrect.getBounds(), paint);
+ } else {
+ SkPath path;
+ path.addRRect(rrect);
+ // call the non-virtual version
+ this->SkCanvas::drawPath(path, paint);
+ }
+}
+
+
void SkCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
+ CHECK_SHADER_NOSETCONTEXT(paint);
+
if (!path.isFinite()) {
return;
}
@@ -1512,6 +1630,8 @@ void SkCanvas::internalDrawBitmapRect(const SkBitmap& bitmap, const SkRect* src,
return;
}
+ CHECK_LOCKCOUNT_BALANCE(bitmap);
+
if (NULL == paint || paint->canComputeFastBounds()) {
SkRect storage;
const SkRect* bounds = &dst;
@@ -1552,6 +1672,7 @@ void SkCanvas::drawBitmapMatrix(const SkBitmap& bitmap, const SkMatrix& matrix,
void SkCanvas::commonDrawBitmap(const SkBitmap& bitmap, const SkIRect* srcRect,
const SkMatrix& matrix, const SkPaint& paint) {
SkDEBUGCODE(bitmap.validate();)
+ CHECK_LOCKCOUNT_BALANCE(bitmap);
LOOPER_BEGIN(paint, SkDrawFilter::kBitmap_Type)
@@ -1731,6 +1852,8 @@ void SkCanvas::DrawTextDecorations(const SkDraw& draw, const SkPaint& paint,
void SkCanvas::drawText(const void* text, size_t byteLength,
SkScalar x, SkScalar y, const SkPaint& paint) {
+ CHECK_SHADER_NOSETCONTEXT(paint);
+
LOOPER_BEGIN(paint, SkDrawFilter::kText_Type)
while (iter.next()) {
@@ -1745,6 +1868,8 @@ void SkCanvas::drawText(const void* text, size_t byteLength,
void SkCanvas::drawPosText(const void* text, size_t byteLength,
const SkPoint pos[], const SkPaint& paint) {
+ CHECK_SHADER_NOSETCONTEXT(paint);
+
LOOPER_BEGIN(paint, SkDrawFilter::kText_Type)
while (iter.next()) {
@@ -1759,6 +1884,8 @@ void SkCanvas::drawPosText(const void* text, size_t byteLength,
void SkCanvas::drawPosTextH(const void* text, size_t byteLength,
const SkScalar xpos[], SkScalar constY,
const SkPaint& paint) {
+ CHECK_SHADER_NOSETCONTEXT(paint);
+
LOOPER_BEGIN(paint, SkDrawFilter::kText_Type)
while (iter.next()) {
@@ -1773,6 +1900,8 @@ void SkCanvas::drawPosTextH(const void* text, size_t byteLength,
void SkCanvas::drawTextOnPath(const void* text, size_t byteLength,
const SkPath& path, const SkMatrix* matrix,
const SkPaint& paint) {
+ CHECK_SHADER_NOSETCONTEXT(paint);
+
LOOPER_BEGIN(paint, SkDrawFilter::kText_Type)
while (iter.next()) {
@@ -1787,6 +1916,8 @@ void SkCanvas::drawTextOnPath(const void* text, size_t byteLength,
void SkCanvas::drawPosTextOnPath(const void* text, size_t byteLength,
const SkPoint pos[], const SkPaint& paint,
const SkPath& path, const SkMatrix* matrix) {
+ CHECK_SHADER_NOSETCONTEXT(paint);
+
LOOPER_BEGIN(paint, SkDrawFilter::kText_Type)
while (iter.next()) {
@@ -1803,6 +1934,8 @@ void SkCanvas::drawVertices(VertexMode vmode, int vertexCount,
const SkColor colors[], SkXfermode* xmode,
const uint16_t indices[], int indexCount,
const SkPaint& paint) {
+ CHECK_SHADER_NOSETCONTEXT(paint);
+
LOOPER_BEGIN(paint, SkDrawFilter::kPath_Type)
while (iter.next()) {
@@ -1886,17 +2019,7 @@ void SkCanvas::drawCircle(SkScalar cx, SkScalar cy, SkScalar radius,
SkRect r;
r.set(cx - radius, cy - radius, cx + radius, cy + radius);
-
- if (paint.canComputeFastBounds()) {
- SkRect storage;
- if (this->quickReject(paint.computeFastBounds(r, &storage))) {
- return;
- }
- }
-
- SkPath path;
- path.addOval(r);
- this->drawPath(path, paint);
+ this->drawOval(r, paint);
}
void SkCanvas::drawRoundRect(const SkRect& r, SkScalar rx, SkScalar ry,
@@ -1908,28 +2031,14 @@ void SkCanvas::drawRoundRect(const SkRect& r, SkScalar rx, SkScalar ry,
return;
}
}
-
- SkPath path;
- path.addRoundRect(r, rx, ry, SkPath::kCW_Direction);
- this->drawPath(path, paint);
+ SkRRect rrect;
+ rrect.setRectXY(r, rx, ry);
+ this->drawRRect(rrect, paint);
} else {
this->drawRect(r, paint);
}
}
-void SkCanvas::drawOval(const SkRect& oval, const SkPaint& paint) {
- if (paint.canComputeFastBounds()) {
- SkRect storage;
- if (this->quickReject(paint.computeFastBounds(oval, &storage))) {
- return;
- }
- }
-
- SkPath path;
- path.addOval(oval);
- this->drawPath(path, paint);
-}
-
void SkCanvas::drawArc(const SkRect& oval, SkScalar startAngle,
SkScalar sweepAngle, bool useCenter,
const SkPaint& paint) {
diff --git a/src/core/SkClipStack.cpp b/src/core/SkClipStack.cpp
index 0f2d632a84..9afef3f311 100644
--- a/src/core/SkClipStack.cpp
+++ b/src/core/SkClipStack.cpp
@@ -13,450 +13,361 @@
// 0-2 are reserved for invalid, empty & wide-open
-int32_t SkClipStack::gGenID = 3;
-
-struct SkClipStack::Rec {
- enum State {
- kEmpty_State,
- kRect_State,
- kPath_State
- };
-
- SkPath fPath;
- SkRect fRect;
- int fSaveCount;
- SkRegion::Op fOp;
- State fState;
- bool fDoAA;
-
- // fFiniteBoundType and fFiniteBound are used to incrementally update
- // the clip stack's bound. When fFiniteBoundType is kNormal_BoundsType,
- // fFiniteBound represents the conservative bounding box of the pixels
- // that aren't clipped (i.e., any pixels that can be drawn to are inside
- // the bound). When fFiniteBoundType is kInsideOut_BoundsType (which occurs
- // when a clip is inverse filled), fFiniteBound represents the
- // conservative bounding box of the pixels that _are_ clipped (i.e., any
- // pixels that cannot be drawn to are inside the bound). When
- // fFiniteBoundType is kInsideOut_BoundsType the actual bound is
- // the infinite plane. This behavior of fFiniteBoundType and
- // fFiniteBound is required so that we can capture the cancelling out
- // of the extensions to infinity when two inverse filled clips are
- // Booleaned together.
- SkClipStack::BoundsType fFiniteBoundType;
- SkRect fFiniteBound;
- bool fIsIntersectionOfRects;
-
- int fGenID;
-
- Rec(int saveCount)
- : fGenID(kInvalidGenID) {
- fSaveCount = saveCount;
- this->setEmpty();
- }
-
- Rec(int saveCount, const SkRect& rect, SkRegion::Op op, bool doAA)
- : fRect(rect)
- , fGenID(kInvalidGenID) {
- fSaveCount = saveCount;
- fOp = op;
- fState = kRect_State;
- fDoAA = doAA;
- // bounding box members are updated in a following updateBound call
- }
-
- Rec(int saveCount, const SkPath& path, SkRegion::Op op, bool doAA)
- : fPath(path)
- , fGenID(kInvalidGenID) {
- fRect.setEmpty();
- fSaveCount = saveCount;
- fOp = op;
- fState = kPath_State;
- fDoAA = doAA;
- // bounding box members are updated in a following updateBound call
- }
-
- void setEmpty() {
- fState = kEmpty_State;
- fFiniteBound.setEmpty();
- fFiniteBoundType = kNormal_BoundsType;
- fIsIntersectionOfRects = false;
- fGenID = kEmptyGenID;
- }
-
- void checkEmpty() {
- SkASSERT(fFiniteBound.isEmpty());
- SkASSERT(kNormal_BoundsType == fFiniteBoundType);
- SkASSERT(!fIsIntersectionOfRects);
- SkASSERT(kEmptyGenID == fGenID);
+static const int32_t kFirstUnreservedGenID = 3;
+int32_t SkClipStack::gGenID = kFirstUnreservedGenID;
+
+void SkClipStack::Element::invertShapeFillType() {
+ switch (fType) {
+ case kRect_Type:
+ fPath.reset();
+ fPath.addRect(fRect);
+ fPath.setFillType(SkPath::kInverseWinding_FillType);
+ fType = kPath_Type;
+ break;
+ case kPath_Type:
+ fPath.toggleInverseFillType();
+ case kEmpty_Type:
+ break;
}
+}
- bool operator==(const Rec& b) const {
- if (fSaveCount != b.fSaveCount ||
- fOp != b.fOp ||
- fState != b.fState ||
- fDoAA != b.fDoAA) {
- return false;
- }
- switch (fState) {
- case kEmpty_State:
- return true;
- case kRect_State:
- return fRect == b.fRect;
- case kPath_State:
- return fPath == b.fPath;
- }
- return false; // Silence the compiler.
- }
+void SkClipStack::Element::checkEmpty() const {
+ SkASSERT(fFiniteBound.isEmpty());
+ SkASSERT(kNormal_BoundsType == fFiniteBoundType);
+ SkASSERT(!fIsIntersectionOfRects);
+ SkASSERT(kEmptyGenID == fGenID);
+ SkASSERT(fPath.isEmpty());
+}
- bool operator!=(const Rec& b) const {
- return !(*this == b);
+bool SkClipStack::Element::canBeIntersectedInPlace(int saveCount, SkRegion::Op op) const {
+ if (kEmpty_Type == fType &&
+ (SkRegion::kDifference_Op == op || SkRegion::kIntersect_Op == op)) {
+ return true;
}
+ // Only clips within the same save/restore frame (as captured by
+ // the save count) can be merged
+ return fSaveCount == saveCount &&
+ SkRegion::kIntersect_Op == op &&
+ (SkRegion::kIntersect_Op == fOp || SkRegion::kReplace_Op == fOp);
+}
+bool SkClipStack::Element::rectRectIntersectAllowed(const SkRect& newR, bool newAA) const {
+ SkASSERT(kRect_Type == fType);
- /**
- * Returns true if this Rec can be intersected in place with a new clip
- */
- bool canBeIntersectedInPlace(int saveCount, SkRegion::Op op) const {
- if (kEmpty_State == fState && (
- SkRegion::kDifference_Op == op ||
- SkRegion::kIntersect_Op == op)) {
- return true;
- }
- // Only clips within the same save/restore frame (as captured by
- // the save count) can be merged
- return fSaveCount == saveCount &&
- SkRegion::kIntersect_Op == op &&
- (SkRegion::kIntersect_Op == fOp || SkRegion::kReplace_Op == fOp);
- }
-
- /**
- * This method checks to see if two rect clips can be safely merged into
- * one. The issue here is that to be strictly correct all the edges of
- * the resulting rect must have the same anti-aliasing.
- */
- bool rectRectIntersectAllowed(const SkRect& newR, bool newAA) const {
- SkASSERT(kRect_State == fState);
-
- if (fDoAA == newAA) {
- // if the AA setting is the same there is no issue
- return true;
- }
-
- if (!SkRect::Intersects(fRect, newR)) {
- // The calling code will correctly set the result to the empty clip
- return true;
- }
-
- if (fRect.contains(newR)) {
- // if the new rect carves out a portion of the old one there is no
- // issue
- return true;
- }
-
- // So either the two overlap in some complex manner or newR contains oldR.
- // In the first, case the edges will require different AA. In the second,
- // the AA setting that would be carried forward is incorrect (e.g., oldR
- // is AA while newR is BW but since newR contains oldR, oldR will be
- // drawn BW) since the new AA setting will predominate.
- return false;
+ if (fDoAA == newAA) {
+ // if the AA setting is the same there is no issue
+ return true;
}
+ if (!SkRect::Intersects(fRect, newR)) {
+ // The calling code will correctly set the result to the empty clip
+ return true;
+ }
- /**
- * The different combination of fill & inverse fill when combining
- * bounding boxes
- */
- enum FillCombo {
- kPrev_Cur_FillCombo,
- kPrev_InvCur_FillCombo,
- kInvPrev_Cur_FillCombo,
- kInvPrev_InvCur_FillCombo
- };
-
- // a mirror of CombineBoundsRevDiff
- void CombineBoundsDiff(FillCombo combination, const SkRect& prevFinite) {
- switch (combination) {
- case kInvPrev_InvCur_FillCombo:
- // In this case the only pixels that can remain set
- // are inside the current clip rect since the extensions
- // to infinity of both clips cancel out and whatever
- // is outside of the current clip is removed
- fFiniteBoundType = kNormal_BoundsType;
- break;
- case kInvPrev_Cur_FillCombo:
- // In this case the current op is finite so the only pixels
- // that aren't set are whatever isn't set in the previous
- // clip and whatever this clip carves out
- fFiniteBound.join(prevFinite);
- fFiniteBoundType = kInsideOut_BoundsType;
- break;
- case kPrev_InvCur_FillCombo:
- // In this case everything outside of this clip's bound
- // is erased, so the only pixels that can remain set
- // occur w/in the intersection of the two finite bounds
- if (!fFiniteBound.intersect(prevFinite)) {
- fFiniteBound.setEmpty();
- }
- fFiniteBoundType = kNormal_BoundsType;
- break;
- case kPrev_Cur_FillCombo:
- // The most conservative result bound is that of the
- // prior clip. This could be wildly incorrect if the
- // second clip either exactly matches the first clip
- // (which should yield the empty set) or reduces the
- // size of the prior bound (e.g., if the second clip
- // exactly matched the bottom half of the prior clip).
- // We ignore these two possibilities.
- fFiniteBound = prevFinite;
- break;
- default:
- SkDEBUGFAIL("SkClipStack::Rec::CombineBoundsDiff Invalid fill combination");
- break;
- }
+ if (fRect.contains(newR)) {
+ // if the new rect carves out a portion of the old one there is no
+ // issue
+ return true;
}
- void CombineBoundsXOR(int combination, const SkRect& prevFinite) {
+ // So either the two overlap in some complex manner or newR contains oldR.
+ // In the first, case the edges will require different AA. In the second,
+ // the AA setting that would be carried forward is incorrect (e.g., oldR
+ // is AA while newR is BW but since newR contains oldR, oldR will be
+ // drawn BW) since the new AA setting will predominate.
+ return false;
+}
- switch (combination) {
- case kInvPrev_Cur_FillCombo: // fall through
- case kPrev_InvCur_FillCombo:
- // With only one of the clips inverted the result will always
- // extend to infinity. The only pixels that may be un-writeable
- // lie within the union of the two finite bounds
- fFiniteBound.join(prevFinite);
- fFiniteBoundType = kInsideOut_BoundsType;
- break;
- case kInvPrev_InvCur_FillCombo:
- // The only pixels that can survive are within the
- // union of the two bounding boxes since the extensions
- // to infinity of both clips cancel out
- // fall through!
- case kPrev_Cur_FillCombo:
- // The most conservative bound for xor is the
- // union of the two bounds. If the two clips exactly overlapped
- // the xor could yield the empty set. Similarly the xor
- // could reduce the size of the original clip's bound (e.g.,
- // if the second clip exactly matched the bottom half of the
- // first clip). We ignore these two cases.
- fFiniteBound.join(prevFinite);
- fFiniteBoundType = kNormal_BoundsType;
- break;
- default:
- SkDEBUGFAIL("SkClipStack::Rec::CombineBoundsXOR Invalid fill combination");
- break;
- }
+// a mirror of combineBoundsRevDiff
+void SkClipStack::Element::combineBoundsDiff(FillCombo combination, const SkRect& prevFinite) {
+ switch (combination) {
+ case kInvPrev_InvCur_FillCombo:
+ // In this case the only pixels that can remain set
+ // are inside the current clip rect since the extensions
+ // to infinity of both clips cancel out and whatever
+ // is outside of the current clip is removed
+ fFiniteBoundType = kNormal_BoundsType;
+ break;
+ case kInvPrev_Cur_FillCombo:
+ // In this case the current op is finite so the only pixels
+ // that aren't set are whatever isn't set in the previous
+ // clip and whatever this clip carves out
+ fFiniteBound.join(prevFinite);
+ fFiniteBoundType = kInsideOut_BoundsType;
+ break;
+ case kPrev_InvCur_FillCombo:
+ // In this case everything outside of this clip's bound
+ // is erased, so the only pixels that can remain set
+ // occur w/in the intersection of the two finite bounds
+ if (!fFiniteBound.intersect(prevFinite)) {
+ fFiniteBound.setEmpty();
+ fGenID = kEmptyGenID;
+ }
+ fFiniteBoundType = kNormal_BoundsType;
+ break;
+ case kPrev_Cur_FillCombo:
+ // The most conservative result bound is that of the
+ // prior clip. This could be wildly incorrect if the
+ // second clip either exactly matches the first clip
+ // (which should yield the empty set) or reduces the
+ // size of the prior bound (e.g., if the second clip
+ // exactly matched the bottom half of the prior clip).
+ // We ignore these two possibilities.
+ fFiniteBound = prevFinite;
+ break;
+ default:
+ SkDEBUGFAIL("SkClipStack::Element::combineBoundsDiff Invalid fill combination");
+ break;
}
+}
- // a mirror of CombineBoundsIntersection
- void CombineBoundsUnion(int combination, const SkRect& prevFinite) {
+void SkClipStack::Element::combineBoundsXOR(int combination, const SkRect& prevFinite) {
- switch (combination) {
- case kInvPrev_InvCur_FillCombo:
- if (!fFiniteBound.intersect(prevFinite)) {
- fFiniteBound.setEmpty();
- }
- fFiniteBoundType = kInsideOut_BoundsType;
- break;
- case kInvPrev_Cur_FillCombo:
- // The only pixels that won't be drawable are inside
- // the prior clip's finite bound
- fFiniteBound = prevFinite;
- fFiniteBoundType = kInsideOut_BoundsType;
- break;
- case kPrev_InvCur_FillCombo:
- // The only pixels that won't be drawable are inside
- // this clip's finite bound
- break;
- case kPrev_Cur_FillCombo:
- fFiniteBound.join(prevFinite);
- break;
- default:
- SkDEBUGFAIL("SkClipStack::Rec::CombineBoundsUnion Invalid fill combination");
- break;
- }
+ switch (combination) {
+ case kInvPrev_Cur_FillCombo: // fall through
+ case kPrev_InvCur_FillCombo:
+ // With only one of the clips inverted the result will always
+ // extend to infinity. The only pixels that may be un-writeable
+ // lie within the union of the two finite bounds
+ fFiniteBound.join(prevFinite);
+ fFiniteBoundType = kInsideOut_BoundsType;
+ break;
+ case kInvPrev_InvCur_FillCombo:
+ // The only pixels that can survive are within the
+ // union of the two bounding boxes since the extensions
+ // to infinity of both clips cancel out
+ // fall through!
+ case kPrev_Cur_FillCombo:
+ // The most conservative bound for xor is the
+ // union of the two bounds. If the two clips exactly overlapped
+ // the xor could yield the empty set. Similarly the xor
+ // could reduce the size of the original clip's bound (e.g.,
+ // if the second clip exactly matched the bottom half of the
+ // first clip). We ignore these two cases.
+ fFiniteBound.join(prevFinite);
+ fFiniteBoundType = kNormal_BoundsType;
+ break;
+ default:
+ SkDEBUGFAIL("SkClipStack::Element::combineBoundsXOR Invalid fill combination");
+ break;
}
+}
- // a mirror of CombineBoundsUnion
- void CombineBoundsIntersection(int combination, const SkRect& prevFinite) {
+// a mirror of combineBoundsIntersection
+void SkClipStack::Element::combineBoundsUnion(int combination, const SkRect& prevFinite) {
- switch (combination) {
- case kInvPrev_InvCur_FillCombo:
- // The only pixels that aren't writable in this case
- // occur in the union of the two finite bounds
- fFiniteBound.join(prevFinite);
- fFiniteBoundType = kInsideOut_BoundsType;
- break;
- case kInvPrev_Cur_FillCombo:
- // In this case the only pixels that will remain writeable
- // are within the current clip
- break;
- case kPrev_InvCur_FillCombo:
- // In this case the only pixels that will remain writeable
- // are with the previous clip
- fFiniteBound = prevFinite;
- fFiniteBoundType = kNormal_BoundsType;
- break;
- case kPrev_Cur_FillCombo:
- if (!fFiniteBound.intersect(prevFinite)) {
- fFiniteBound.setEmpty();
- }
- break;
- default:
- SkDEBUGFAIL("SkClipStack::Rec::CombineBoundsIntersection Invalid fill combination");
- break;
- }
+ switch (combination) {
+ case kInvPrev_InvCur_FillCombo:
+ if (!fFiniteBound.intersect(prevFinite)) {
+ fFiniteBound.setEmpty();
+ fGenID = kWideOpenGenID;
+ }
+ fFiniteBoundType = kInsideOut_BoundsType;
+ break;
+ case kInvPrev_Cur_FillCombo:
+ // The only pixels that won't be drawable are inside
+ // the prior clip's finite bound
+ fFiniteBound = prevFinite;
+ fFiniteBoundType = kInsideOut_BoundsType;
+ break;
+ case kPrev_InvCur_FillCombo:
+ // The only pixels that won't be drawable are inside
+ // this clip's finite bound
+ break;
+ case kPrev_Cur_FillCombo:
+ fFiniteBound.join(prevFinite);
+ break;
+ default:
+ SkDEBUGFAIL("SkClipStack::Element::combineBoundsUnion Invalid fill combination");
+ break;
}
+}
- // a mirror of CombineBoundsDiff
- void CombineBoundsRevDiff(int combination, const SkRect& prevFinite) {
+// a mirror of combineBoundsUnion
+void SkClipStack::Element::combineBoundsIntersection(int combination, const SkRect& prevFinite) {
- switch (combination) {
- case kInvPrev_InvCur_FillCombo:
- // The only pixels that can survive are in the
- // previous bound since the extensions to infinity in
- // both clips cancel out
- fFiniteBound = prevFinite;
- fFiniteBoundType = kNormal_BoundsType;
- break;
- case kInvPrev_Cur_FillCombo:
- if (!fFiniteBound.intersect(prevFinite)) {
- fFiniteBound.setEmpty();
- }
- fFiniteBoundType = kNormal_BoundsType;
- break;
- case kPrev_InvCur_FillCombo:
- fFiniteBound.join(prevFinite);
- fFiniteBoundType = kInsideOut_BoundsType;
- break;
- case kPrev_Cur_FillCombo:
- // Fall through - as with the kDifference_Op case, the
- // most conservative result bound is the bound of the
- // current clip. The prior clip could reduce the size of this
- // bound (as in the kDifference_Op case) but we are ignoring
- // those cases.
- break;
- default:
- SkDEBUGFAIL("SkClipStack::Rec::CombineBoundsRevDiff Invalid fill combination");
- break;
- }
+ switch (combination) {
+ case kInvPrev_InvCur_FillCombo:
+ // The only pixels that aren't writable in this case
+ // occur in the union of the two finite bounds
+ fFiniteBound.join(prevFinite);
+ fFiniteBoundType = kInsideOut_BoundsType;
+ break;
+ case kInvPrev_Cur_FillCombo:
+ // In this case the only pixels that will remain writeable
+ // are within the current clip
+ break;
+ case kPrev_InvCur_FillCombo:
+ // In this case the only pixels that will remain writeable
+ // are with the previous clip
+ fFiniteBound = prevFinite;
+ fFiniteBoundType = kNormal_BoundsType;
+ break;
+ case kPrev_Cur_FillCombo:
+ if (!fFiniteBound.intersect(prevFinite)) {
+ fFiniteBound.setEmpty();
+ fGenID = kEmptyGenID;
+ }
+ break;
+ default:
+ SkDEBUGFAIL("SkClipStack::Element::combineBoundsIntersection Invalid fill combination");
+ break;
}
+}
- void updateBound(const Rec* prior) {
+// a mirror of combineBoundsDiff
+void SkClipStack::Element::combineBoundsRevDiff(int combination, const SkRect& prevFinite) {
- // First, optimistically update the current Rec's bound information
- // with the current clip's bound
- fIsIntersectionOfRects = false;
- if (kRect_State == fState) {
- fFiniteBound = fRect;
+ switch (combination) {
+ case kInvPrev_InvCur_FillCombo:
+ // The only pixels that can survive are in the
+ // previous bound since the extensions to infinity in
+ // both clips cancel out
+ fFiniteBound = prevFinite;
fFiniteBoundType = kNormal_BoundsType;
-
- if (SkRegion::kReplace_Op == fOp ||
- (SkRegion::kIntersect_Op == fOp && NULL == prior) ||
- (SkRegion::kIntersect_Op == fOp && prior->fIsIntersectionOfRects &&
- prior->rectRectIntersectAllowed(fRect, fDoAA))) {
- fIsIntersectionOfRects = true;
+ break;
+ case kInvPrev_Cur_FillCombo:
+ if (!fFiniteBound.intersect(prevFinite)) {
+ fFiniteBound.setEmpty();
+ fGenID = kEmptyGenID;
}
+ fFiniteBoundType = kNormal_BoundsType;
+ break;
+ case kPrev_InvCur_FillCombo:
+ fFiniteBound.join(prevFinite);
+ fFiniteBoundType = kInsideOut_BoundsType;
+ break;
+ case kPrev_Cur_FillCombo:
+ // Fall through - as with the kDifference_Op case, the
+ // most conservative result bound is the bound of the
+ // current clip. The prior clip could reduce the size of this
+ // bound (as in the kDifference_Op case) but we are ignoring
+ // those cases.
+ break;
+ default:
+ SkDEBUGFAIL("SkClipStack::Element::combineBoundsRevDiff Invalid fill combination");
+ break;
+ }
+}
- } else {
- SkASSERT(kPath_State == fState);
+void SkClipStack::Element::updateBoundAndGenID(const Element* prior) {
+ // We set this first here but we may overwrite it later if we determine that the clip is
+ // either wide-open or empty.
+ fGenID = GetNextGenID();
- fFiniteBound = fPath.getBounds();
+ // First, optimistically update the current Element's bound information
+ // with the current clip's bound
+ fIsIntersectionOfRects = false;
+ if (kRect_Type == fType) {
+ fFiniteBound = fRect;
+ fFiniteBoundType = kNormal_BoundsType;
- if (fPath.isInverseFillType()) {
- fFiniteBoundType = kInsideOut_BoundsType;
- } else {
- fFiniteBoundType = kNormal_BoundsType;
- }
+ if (SkRegion::kReplace_Op == fOp ||
+ (SkRegion::kIntersect_Op == fOp && NULL == prior) ||
+ (SkRegion::kIntersect_Op == fOp && prior->fIsIntersectionOfRects &&
+ prior->rectRectIntersectAllowed(fRect, fDoAA))) {
+ fIsIntersectionOfRects = true;
}
- if (!fDoAA) {
- // Here we mimic a non-anti-aliased scanline system. If there is
- // no anti-aliasing we can integerize the bounding box to exclude
- // fractional parts that won't be rendered.
- // Note: the left edge is handled slightly differently below. We
- // are a bit more generous in the rounding since we don't want to
- // risk missing the left pixels when fLeft is very close to .5
- fFiniteBound.set(SkIntToScalar(SkScalarFloorToInt(fFiniteBound.fLeft+0.45f)),
- SkIntToScalar(SkScalarRound(fFiniteBound.fTop)),
- SkIntToScalar(SkScalarRound(fFiniteBound.fRight)),
- SkIntToScalar(SkScalarRound(fFiniteBound.fBottom)));
- }
+ } else {
+ SkASSERT(kPath_Type == fType);
- // Now set up the previous Rec's bound information taking into
- // account that there may be no previous clip
- SkRect prevFinite;
- SkClipStack::BoundsType prevType;
+ fFiniteBound = fPath.getBounds();
- if (NULL == prior) {
- // no prior clip means the entire plane is writable
- prevFinite.setEmpty(); // there are no pixels that cannot be drawn to
- prevType = kInsideOut_BoundsType;
+ if (fPath.isInverseFillType()) {
+ fFiniteBoundType = kInsideOut_BoundsType;
} else {
- prevFinite = prior->fFiniteBound;
- prevType = prior->fFiniteBoundType;
+ fFiniteBoundType = kNormal_BoundsType;
}
+ }
- FillCombo combination = kPrev_Cur_FillCombo;
- if (kInsideOut_BoundsType == fFiniteBoundType) {
- combination = (FillCombo) (combination | 0x01);
- }
- if (kInsideOut_BoundsType == prevType) {
- combination = (FillCombo) (combination | 0x02);
- }
+ if (!fDoAA) {
+ // Here we mimic a non-anti-aliased scanline system. If there is
+ // no anti-aliasing we can integerize the bounding box to exclude
+ // fractional parts that won't be rendered.
+ // Note: the left edge is handled slightly differently below. We
+ // are a bit more generous in the rounding since we don't want to
+ // risk missing the left pixels when fLeft is very close to .5
+ fFiniteBound.set(SkIntToScalar(SkScalarFloorToInt(fFiniteBound.fLeft+0.45f)),
+ SkIntToScalar(SkScalarRound(fFiniteBound.fTop)),
+ SkIntToScalar(SkScalarRound(fFiniteBound.fRight)),
+ SkIntToScalar(SkScalarRound(fFiniteBound.fBottom)));
+ }
+
+ // Now determine the previous Element's bound information taking into
+ // account that there may be no previous clip
+ SkRect prevFinite;
+ SkClipStack::BoundsType prevType;
+
+ if (NULL == prior) {
+ // no prior clip means the entire plane is writable
+ prevFinite.setEmpty(); // there are no pixels that cannot be drawn to
+ prevType = kInsideOut_BoundsType;
+ } else {
+ prevFinite = prior->fFiniteBound;
+ prevType = prior->fFiniteBoundType;
+ }
- SkASSERT(kInvPrev_InvCur_FillCombo == combination ||
- kInvPrev_Cur_FillCombo == combination ||
- kPrev_InvCur_FillCombo == combination ||
- kPrev_Cur_FillCombo == combination);
+ FillCombo combination = kPrev_Cur_FillCombo;
+ if (kInsideOut_BoundsType == fFiniteBoundType) {
+ combination = (FillCombo) (combination | 0x01);
+ }
+ if (kInsideOut_BoundsType == prevType) {
+ combination = (FillCombo) (combination | 0x02);
+ }
- // Now integrate with clip with the prior clips
- switch (fOp) {
- case SkRegion::kDifference_Op:
- this->CombineBoundsDiff(combination, prevFinite);
- break;
- case SkRegion::kXOR_Op:
- this->CombineBoundsXOR(combination, prevFinite);
- break;
- case SkRegion::kUnion_Op:
- this->CombineBoundsUnion(combination, prevFinite);
- break;
- case SkRegion::kIntersect_Op:
- this->CombineBoundsIntersection(combination, prevFinite);
- break;
- case SkRegion::kReverseDifference_Op:
- this->CombineBoundsRevDiff(combination, prevFinite);
- break;
- case SkRegion::kReplace_Op:
- // Replace just ignores everything prior
- // The current clip's bound information is already filled in
- // so nothing to do
- break;
- default:
- SkDebugf("SkRegion::Op error/n");
- SkASSERT(0);
- break;
- }
+ SkASSERT(kInvPrev_InvCur_FillCombo == combination ||
+ kInvPrev_Cur_FillCombo == combination ||
+ kPrev_InvCur_FillCombo == combination ||
+ kPrev_Cur_FillCombo == combination);
+
+ // Now integrate with clip with the prior clips
+ switch (fOp) {
+ case SkRegion::kDifference_Op:
+ this->combineBoundsDiff(combination, prevFinite);
+ break;
+ case SkRegion::kXOR_Op:
+ this->combineBoundsXOR(combination, prevFinite);
+ break;
+ case SkRegion::kUnion_Op:
+ this->combineBoundsUnion(combination, prevFinite);
+ break;
+ case SkRegion::kIntersect_Op:
+ this->combineBoundsIntersection(combination, prevFinite);
+ break;
+ case SkRegion::kReverseDifference_Op:
+ this->combineBoundsRevDiff(combination, prevFinite);
+ break;
+ case SkRegion::kReplace_Op:
+ // Replace just ignores everything prior
+ // The current clip's bound information is already filled in
+ // so nothing to do
+ break;
+ default:
+ SkDebugf("SkRegion::Op error/n");
+ SkASSERT(0);
+ break;
}
-};
+}
-// This constant determines how many Rec's are allocated together as a block in
+// This constant determines how many Element's are allocated together as a block in
// the deque. As such it needs to balance allocating too much memory vs.
// incurring allocation/deallocation thrashing. It should roughly correspond to
// the deepest save/restore stack we expect to see.
-static const int kDefaultRecordAllocCnt = 8;
+static const int kDefaultElementAllocCnt = 8;
SkClipStack::SkClipStack()
- : fDeque(sizeof(Rec), kDefaultRecordAllocCnt)
+ : fDeque(sizeof(Element), kDefaultElementAllocCnt)
, fSaveCount(0) {
}
SkClipStack::SkClipStack(const SkClipStack& b)
- : fDeque(sizeof(Rec), kDefaultRecordAllocCnt) {
+ : fDeque(sizeof(Element), kDefaultElementAllocCnt) {
*this = b;
}
SkClipStack::SkClipStack(const SkRect& r)
- : fDeque(sizeof(Rec), kDefaultRecordAllocCnt)
+ : fDeque(sizeof(Element), kDefaultElementAllocCnt)
, fSaveCount(0) {
if (!r.isEmpty()) {
this->clipDevRect(r, SkRegion::kReplace_Op, false);
@@ -464,7 +375,7 @@ SkClipStack::SkClipStack(const SkRect& r)
}
SkClipStack::SkClipStack(const SkIRect& r)
- : fDeque(sizeof(Rec), kDefaultRecordAllocCnt)
+ : fDeque(sizeof(Element), kDefaultElementAllocCnt)
, fSaveCount(0) {
if (!r.isEmpty()) {
SkRect temp;
@@ -485,10 +396,10 @@ SkClipStack& SkClipStack::operator=(const SkClipStack& b) {
fSaveCount = b.fSaveCount;
SkDeque::F2BIter recIter(b.fDeque);
- for (const Rec* rec = (const Rec*)recIter.next();
- rec != NULL;
- rec = (const Rec*)recIter.next()) {
- new (fDeque.push_back()) Rec(*rec);
+ for (const Element* element = (const Element*)recIter.next();
+ element != NULL;
+ element = (const Element*)recIter.next()) {
+ new (fDeque.push_back()) Element(*element);
}
return *this;
@@ -501,25 +412,25 @@ bool SkClipStack::operator==(const SkClipStack& b) const {
}
SkDeque::F2BIter myIter(fDeque);
SkDeque::F2BIter bIter(b.fDeque);
- const Rec* myRec = (const Rec*)myIter.next();
- const Rec* bRec = (const Rec*)bIter.next();
+ const Element* myElement = (const Element*)myIter.next();
+ const Element* bElement = (const Element*)bIter.next();
- while (myRec != NULL && bRec != NULL) {
- if (*myRec != *bRec) {
+ while (myElement != NULL && bElement != NULL) {
+ if (*myElement != *bElement) {
return false;
}
- myRec = (const Rec*)myIter.next();
- bRec = (const Rec*)bIter.next();
+ myElement = (const Element*)myIter.next();
+ bElement = (const Element*)bIter.next();
}
- return myRec == NULL && bRec == NULL;
+ return myElement == NULL && bElement == NULL;
}
void SkClipStack::reset() {
// We used a placement new for each object in fDeque, so we're responsible
// for calling the destructor on each of them as well.
while (!fDeque.empty()) {
- Rec* rec = (Rec*)fDeque.back();
- rec->~Rec();
+ Element* element = (Element*)fDeque.back();
+ element->~Element();
fDeque.pop_back();
}
@@ -533,12 +444,12 @@ void SkClipStack::save() {
void SkClipStack::restore() {
fSaveCount -= 1;
while (!fDeque.empty()) {
- Rec* rec = (Rec*)fDeque.back();
- if (rec->fSaveCount <= fSaveCount) {
+ Element* element = (Element*)fDeque.back();
+ if (element->fSaveCount <= fSaveCount) {
break;
}
- this->purgeClip(rec);
- rec->~Rec();
+ this->purgeClip(element);
+ element->~Element();
fDeque.pop_back();
}
}
@@ -548,9 +459,9 @@ void SkClipStack::getBounds(SkRect* canvFiniteBound,
bool* isIntersectionOfRects) const {
SkASSERT(NULL != canvFiniteBound && NULL != boundType);
- Rec* rec = (Rec*)fDeque.back();
+ Element* element = (Element*)fDeque.back();
- if (NULL == rec) {
+ if (NULL == element) {
// the clip is wide open - the infinite plane w/ no pixels un-writeable
canvFiniteBound->setEmpty();
*boundType = kInsideOut_BoundsType;
@@ -560,10 +471,10 @@ void SkClipStack::getBounds(SkRect* canvFiniteBound,
return;
}
- *canvFiniteBound = rec->fFiniteBound;
- *boundType = rec->fFiniteBoundType;
+ *canvFiniteBound = element->fFiniteBound;
+ *boundType = element->fFiniteBoundType;
if (NULL != isIntersectionOfRects) {
- *isIntersectionOfRects = rec->fIsIntersectionOfRects;
+ *isIntersectionOfRects = element->fIsIntersectionOfRects;
}
}
@@ -586,113 +497,132 @@ bool SkClipStack::intersectRectWithClip(SkRect* rect) const {
}
}
-void SkClipStack::clipDevRect(const SkRect& rect, SkRegion::Op op, bool doAA) {
+bool SkClipStack::quickContains(const SkRect& rect) const {
+
+ Iter iter(*this, Iter::kTop_IterStart);
+ const Element* element = iter.prev();
+ while (element != NULL) {
+ if (SkRegion::kIntersect_Op != element->getOp() && SkRegion::kReplace_Op != element->getOp())
+ return false;
+ if (element->isInverseFilled()) {
+ // Part of 'rect' could be trimmed off by the inverse-filled clip element
+ if (SkRect::Intersects(element->getBounds(), rect)) {
+ return false;
+ }
+ } else {
+ if (!element->contains(rect)) {
+ return false;
+ }
+ }
+ if (SkRegion::kReplace_Op == element->getOp()) {
+ break;
+ }
+ element = iter.prev();
+ }
+ return true;
+}
- int32_t genID = GetNextGenID();
+void SkClipStack::clipDevRect(const SkRect& rect, SkRegion::Op op, bool doAA) {
// Use reverse iterator instead of back because Rect path may need previous
SkDeque::Iter iter(fDeque, SkDeque::Iter::kBack_IterStart);
- Rec* rec = (Rec*) iter.prev();
+ Element* element = (Element*) iter.prev();
- if (rec && rec->canBeIntersectedInPlace(fSaveCount, op)) {
- switch (rec->fState) {
- case Rec::kEmpty_State:
- rec->checkEmpty();
+ if (element && element->canBeIntersectedInPlace(fSaveCount, op)) {
+ switch (element->fType) {
+ case Element::kEmpty_Type:
+ element->checkEmpty();
return;
- case Rec::kRect_State:
- if (rec->rectRectIntersectAllowed(rect, doAA)) {
- this->purgeClip(rec);
- if (!rec->fRect.intersect(rect)) {
- rec->setEmpty();
+ case Element::kRect_Type:
+ if (element->rectRectIntersectAllowed(rect, doAA)) {
+ this->purgeClip(element);
+ if (!element->fRect.intersect(rect)) {
+ element->setEmpty();
return;
}
- rec->fDoAA = doAA;
- Rec* prev = (Rec*) iter.prev();
- rec->updateBound(prev);
- rec->fGenID = genID;
+ element->fDoAA = doAA;
+ Element* prev = (Element*) iter.prev();
+ element->updateBoundAndGenID(prev);
return;
}
break;
- case Rec::kPath_State:
- if (!SkRect::Intersects(rec->fPath.getBounds(), rect)) {
- this->purgeClip(rec);
- rec->setEmpty();
+ case Element::kPath_Type:
+ if (!SkRect::Intersects(element->fPath.getBounds(), rect)) {
+ this->purgeClip(element);
+ element->setEmpty();
return;
}
break;
}
}
- new (fDeque.push_back()) Rec(fSaveCount, rect, op, doAA);
- ((Rec*) fDeque.back())->updateBound(rec);
- ((Rec*) fDeque.back())->fGenID = genID;
+ new (fDeque.push_back()) Element(fSaveCount, rect, op, doAA);
+ ((Element*) fDeque.back())->updateBoundAndGenID(element);
- if (rec && rec->fSaveCount == fSaveCount) {
- this->purgeClip(rec);
+ if (element && element->fSaveCount == fSaveCount) {
+ this->purgeClip(element);
}
}
void SkClipStack::clipDevPath(const SkPath& path, SkRegion::Op op, bool doAA) {
SkRect alt;
- if (path.isRect(&alt)) {
+ if (path.isRect(&alt) && !path.isInverseFillType()) {
return this->clipDevRect(alt, op, doAA);
}
- int32_t genID = GetNextGenID();
-
- Rec* rec = (Rec*)fDeque.back();
- if (rec && rec->canBeIntersectedInPlace(fSaveCount, op)) {
+ Element* element = (Element*)fDeque.back();
+ if (element && element->canBeIntersectedInPlace(fSaveCount, op)) {
const SkRect& pathBounds = path.getBounds();
- switch (rec->fState) {
- case Rec::kEmpty_State:
- rec->checkEmpty();
+ switch (element->fType) {
+ case Element::kEmpty_Type:
+ element->checkEmpty();
return;
- case Rec::kRect_State:
- if (!SkRect::Intersects(rec->fRect, pathBounds)) {
- this->purgeClip(rec);
- rec->setEmpty();
+ case Element::kRect_Type:
+ if (!SkRect::Intersects(element->fRect, pathBounds)) {
+ this->purgeClip(element);
+ element->setEmpty();
return;
}
break;
- case Rec::kPath_State:
- if (!SkRect::Intersects(rec->fPath.getBounds(), pathBounds)) {
- this->purgeClip(rec);
- rec->setEmpty();
+ case Element::kPath_Type:
+ if (!SkRect::Intersects(element->fPath.getBounds(), pathBounds)) {
+ this->purgeClip(element);
+ element->setEmpty();
return;
}
break;
}
}
- new (fDeque.push_back()) Rec(fSaveCount, path, op, doAA);
- ((Rec*) fDeque.back())->updateBound(rec);
- ((Rec*) fDeque.back())->fGenID = genID;
+ new (fDeque.push_back()) Element(fSaveCount, path, op, doAA);
+ ((Element*) fDeque.back())->updateBoundAndGenID(element);
- if (rec && rec->fSaveCount == fSaveCount) {
- this->purgeClip(rec);
+ if (element && element->fSaveCount == fSaveCount) {
+ this->purgeClip(element);
}
}
void SkClipStack::clipEmpty() {
- Rec* rec = (Rec*) fDeque.back();
+ Element* element = (Element*) fDeque.back();
- if (rec && rec->canBeIntersectedInPlace(fSaveCount, SkRegion::kIntersect_Op)) {
- switch (rec->fState) {
- case Rec::kEmpty_State:
- rec->checkEmpty();
+ if (element && element->canBeIntersectedInPlace(fSaveCount, SkRegion::kIntersect_Op)) {
+ switch (element->fType) {
+ case Element::kEmpty_Type:
+ element->checkEmpty();
return;
- case Rec::kRect_State:
- case Rec::kPath_State:
- this->purgeClip(rec);
- rec->setEmpty();
+ case Element::kRect_Type:
+ case Element::kPath_Type:
+ this->purgeClip(element);
+ element->setEmpty();
return;
}
}
- new (fDeque.push_back()) Rec(fSaveCount);
+ new (fDeque.push_back()) Element(fSaveCount);
- if (rec && rec->fSaveCount == fSaveCount) {
- this->purgeClip(rec);
+ if (element && element->fSaveCount == fSaveCount) {
+ this->purgeClip(element);
}
+ ((Element*)fDeque.back())->fGenID = kEmptyGenID;
}
bool SkClipStack::isWideOpen() const {
@@ -700,9 +630,9 @@ bool SkClipStack::isWideOpen() const {
return true;
}
- const Rec* back = (const Rec*) fDeque.back();
- return kInsideOut_BoundsType == back->fFiniteBoundType &&
- back->fFiniteBound.isEmpty();
+ const Element* back = (const Element*) fDeque.back();
+ return kWideOpenGenID == back->fGenID ||
+ (kInsideOut_BoundsType == back->fFiniteBoundType && back->fFiniteBound.isEmpty());
}
///////////////////////////////////////////////////////////////////////////////
@@ -710,66 +640,20 @@ bool SkClipStack::isWideOpen() const {
SkClipStack::Iter::Iter() : fStack(NULL) {
}
-bool operator==(const SkClipStack::Iter::Clip& a,
- const SkClipStack::Iter::Clip& b) {
- return a.fOp == b.fOp && a.fDoAA == b.fDoAA &&
- ((a.fRect == NULL && b.fRect == NULL) ||
- (a.fRect != NULL && b.fRect != NULL && *a.fRect == *b.fRect)) &&
- ((a.fPath == NULL && b.fPath == NULL) ||
- (a.fPath != NULL && b.fPath != NULL && *a.fPath == *b.fPath));
-}
-
-bool operator!=(const SkClipStack::Iter::Clip& a,
- const SkClipStack::Iter::Clip& b) {
- return !(a == b);
-}
-
SkClipStack::Iter::Iter(const SkClipStack& stack, IterStart startLoc)
: fStack(&stack) {
this->reset(stack, startLoc);
}
-const SkClipStack::Iter::Clip* SkClipStack::Iter::updateClip(
- const SkClipStack::Rec* rec) {
- switch (rec->fState) {
- case SkClipStack::Rec::kEmpty_State:
- fClip.fRect = NULL;
- fClip.fPath = NULL;
- break;
- case SkClipStack::Rec::kRect_State:
- fClip.fRect = &rec->fRect;
- fClip.fPath = NULL;
- break;
- case SkClipStack::Rec::kPath_State:
- fClip.fRect = NULL;
- fClip.fPath = &rec->fPath;
- break;
- }
- fClip.fOp = rec->fOp;
- fClip.fDoAA = rec->fDoAA;
- fClip.fGenID = rec->fGenID;
- return &fClip;
-}
-
-const SkClipStack::Iter::Clip* SkClipStack::Iter::next() {
- const SkClipStack::Rec* rec = (const SkClipStack::Rec*)fIter.next();
- if (NULL == rec) {
- return NULL;
- }
-
- return this->updateClip(rec);
+const SkClipStack::Element* SkClipStack::Iter::next() {
+ return (const SkClipStack::Element*)fIter.next();
}
-const SkClipStack::Iter::Clip* SkClipStack::Iter::prev() {
- const SkClipStack::Rec* rec = (const SkClipStack::Rec*)fIter.prev();
- if (NULL == rec) {
- return NULL;
- }
-
- return this->updateClip(rec);
+const SkClipStack::Element* SkClipStack::Iter::prev() {
+ return (const SkClipStack::Element*)fIter.prev();
}
-const SkClipStack::Iter::Clip* SkClipStack::Iter::skipToTopmost(SkRegion::Op op) {
+const SkClipStack::Element* SkClipStack::Iter::skipToTopmost(SkRegion::Op op) {
if (NULL == fStack) {
return NULL;
@@ -777,15 +661,15 @@ const SkClipStack::Iter::Clip* SkClipStack::Iter::skipToTopmost(SkRegion::Op op)
fIter.reset(fStack->fDeque, SkDeque::Iter::kBack_IterStart);
- const SkClipStack::Rec* rec = NULL;
+ const SkClipStack::Element* element = NULL;
- for (rec = (const SkClipStack::Rec*) fIter.prev();
- NULL != rec;
- rec = (const SkClipStack::Rec*) fIter.prev()) {
+ for (element = (const SkClipStack::Element*) fIter.prev();
+ NULL != element;
+ element = (const SkClipStack::Element*) fIter.prev()) {
- if (op == rec->fOp) {
+ if (op == element->fOp) {
// The Deque's iterator is actually one pace ahead of the
- // returned value. So while "rec" is the element we want to
+ // returned value. So while "element" is the element we want to
// return, the iterator is actually pointing at (and will
// return on the next "next" or "prev" call) the element
// in front of it in the deque. Bump the iterator forward a
@@ -800,7 +684,7 @@ const SkClipStack::Iter::Clip* SkClipStack::Iter::skipToTopmost(SkRegion::Op op)
}
}
- if (NULL == rec) {
+ if (NULL == element) {
// There were no "op" clips
fIter.reset(fStack->fDeque, SkDeque::Iter::kFront_IterStart);
}
@@ -855,19 +739,23 @@ void SkClipStack::removePurgeClipCallback(PFPurgeClipCB callback, void* data) co
}
}
-// The clip state represented by 'rec' will never be used again. Purge it.
-void SkClipStack::purgeClip(Rec* rec) {
- SkASSERT(NULL != rec);
+// The clip state represented by 'element' will never be used again. Purge it.
+void SkClipStack::purgeClip(Element* element) {
+ SkASSERT(NULL != element);
+ if (element->fGenID >= 0 && element->fGenID < kFirstUnreservedGenID) {
+ return;
+ }
for (int i = 0; i < fCallbackData.count(); ++i) {
- (*fCallbackData[i].fCallback)(rec->fGenID, fCallbackData[i].fData);
+ (*fCallbackData[i].fCallback)(element->fGenID, fCallbackData[i].fData);
}
- // Invalidate rec's gen ID so handlers can detect already handled records
- rec->fGenID = kInvalidGenID;
+ // Invalidate element's gen ID so handlers can detect already handled records
+ element->fGenID = kInvalidGenID;
}
int32_t SkClipStack::GetNextGenID() {
+ // TODO: handle overflow.
return sk_atomic_inc(&gGenID);
}
@@ -877,6 +765,6 @@ int32_t SkClipStack::getTopmostGenID() const {
return kInvalidGenID;
}
- Rec* rec = (Rec*)fDeque.back();
- return rec->fGenID;
+ Element* element = (Element*)fDeque.back();
+ return element->fGenID;
}
diff --git a/src/core/SkColorFilter.cpp b/src/core/SkColorFilter.cpp
index fee34ed805..747b2fff6c 100644
--- a/src/core/SkColorFilter.cpp
+++ b/src/core/SkColorFilter.cpp
@@ -13,19 +13,19 @@
SK_DEFINE_INST_COUNT(SkColorFilter)
-bool SkColorFilter::asColorMode(SkColor* color, SkXfermode::Mode* mode) {
+bool SkColorFilter::asColorMode(SkColor* color, SkXfermode::Mode* mode) const {
return false;
}
-bool SkColorFilter::asColorMatrix(SkScalar matrix[20]) {
+bool SkColorFilter::asColorMatrix(SkScalar matrix[20]) const {
return false;
}
-bool SkColorFilter::asComponentTable(SkBitmap*) {
+bool SkColorFilter::asComponentTable(SkBitmap*) const {
return false;
}
-void SkColorFilter::filterSpan16(const uint16_t s[], int count, uint16_t d[]) {
+void SkColorFilter::filterSpan16(const uint16_t s[], int count, uint16_t d[]) const {
SkASSERT(this->getFlags() & SkColorFilter::kHasFilter16_Flag);
SkDEBUGFAIL("missing implementation of SkColorFilter::filterSpan16");
@@ -34,12 +34,16 @@ void SkColorFilter::filterSpan16(const uint16_t s[], int count, uint16_t d[]) {
}
}
-SkColor SkColorFilter::filterColor(SkColor c) {
+SkColor SkColorFilter::filterColor(SkColor c) const {
SkPMColor dst, src = SkPreMultiplyColor(c);
this->filterSpan(&src, 1, &dst);
return SkUnPreMultiply::PMColorToColor(dst);
}
+GrEffect* SkColorFilter::asNewEffect(GrContext*) const {
+ return NULL;
+}
+
///////////////////////////////////////////////////////////////////////////////
SkFilterShader::SkFilterShader(SkShader* shader, SkColorFilter* filter) {
@@ -58,16 +62,6 @@ SkFilterShader::~SkFilterShader() {
fShader->unref();
}
-void SkFilterShader::beginSession() {
- this->INHERITED::beginSession();
- fShader->beginSession();
-}
-
-void SkFilterShader::endSession() {
- fShader->endSession();
- this->INHERITED::endSession();
-}
-
void SkFilterShader::flatten(SkFlattenableWriteBuffer& buffer) const {
this->INHERITED::flatten(buffer);
buffer.writeFlattenable(fShader);
@@ -92,8 +86,22 @@ uint32_t SkFilterShader::getFlags() {
bool SkFilterShader::setContext(const SkBitmap& device,
const SkPaint& paint,
const SkMatrix& matrix) {
- return this->INHERITED::setContext(device, paint, matrix) &&
- fShader->setContext(device, paint, matrix);
+ // we need to keep the setContext/endContext calls balanced. If we return
+ // false, our endContext() will not be called.
+
+ if (!this->INHERITED::setContext(device, paint, matrix)) {
+ return false;
+ }
+ if (!fShader->setContext(device, paint, matrix)) {
+ this->INHERITED::endContext();
+ return false;
+ }
+ return true;
+}
+
+void SkFilterShader::endContext() {
+ fShader->endContext();
+ this->INHERITED::endContext();
}
void SkFilterShader::shadeSpan(int x, int y, SkPMColor result[], int count) {
diff --git a/src/core/SkComposeShader.cpp b/src/core/SkComposeShader.cpp
index e925ab5627..92ffaf7d91 100644
--- a/src/core/SkComposeShader.cpp
+++ b/src/core/SkComposeShader.cpp
@@ -43,18 +43,6 @@ SkComposeShader::~SkComposeShader() {
fShaderA->unref();
}
-void SkComposeShader::beginSession() {
- this->INHERITED::beginSession();
- fShaderA->beginSession();
- fShaderB->beginSession();
-}
-
-void SkComposeShader::endSession() {
- fShaderA->endSession();
- fShaderB->endSession();
- this->INHERITED::endSession();
-}
-
class SkAutoAlphaRestore {
public:
SkAutoAlphaRestore(SkPaint* paint, uint8_t newAlpha) {
@@ -81,7 +69,10 @@ void SkComposeShader::flatten(SkFlattenableWriteBuffer& buffer) const {
/* We call setContext on our two worker shaders. However, we
always let them see opaque alpha, and if the paint really
is translucent, then we apply that after the fact.
-*/
+
+ We need to keep the calls to setContext/endContext balanced, since if we
+ return false, our endContext() will not be called.
+ */
bool SkComposeShader::setContext(const SkBitmap& device,
const SkPaint& paint,
const SkMatrix& matrix) {
@@ -94,13 +85,29 @@ bool SkComposeShader::setContext(const SkBitmap& device,
SkMatrix tmpM;
- (void)this->getLocalMatrix(&tmpM);
- tmpM.setConcat(matrix, tmpM);
+ tmpM.setConcat(matrix, this->getLocalMatrix());
SkAutoAlphaRestore restore(const_cast<SkPaint*>(&paint), 0xFF);
- return fShaderA->setContext(device, paint, tmpM) &&
- fShaderB->setContext(device, paint, tmpM);
+ bool setContextA = fShaderA->setContext(device, paint, tmpM);
+ bool setContextB = fShaderB->setContext(device, paint, tmpM);
+ if (!setContextA || !setContextB) {
+ if (setContextB) {
+ fShaderB->endContext();
+ }
+ else if (setContextA) {
+ fShaderA->endContext();
+ }
+ this->INHERITED::endContext();
+ return false;
+ }
+ return true;
+}
+
+void SkComposeShader::endContext() {
+ fShaderB->endContext();
+ fShaderA->endContext();
+ this->INHERITED::endContext();
}
// larger is better (fewer times we have to loop), but we shouldn't
diff --git a/src/core/SkCoreBlitters.h b/src/core/SkCoreBlitters.h
index 48f85b0475..47a0aa4a57 100644
--- a/src/core/SkCoreBlitters.h
+++ b/src/core/SkCoreBlitters.h
@@ -129,16 +129,19 @@ class SkARGB32_Shader_Blitter : public SkShaderBlitter {
public:
SkARGB32_Shader_Blitter(const SkBitmap& device, const SkPaint& paint);
virtual ~SkARGB32_Shader_Blitter();
- virtual void blitH(int x, int y, int width);
+ virtual void blitH(int x, int y, int width) SK_OVERRIDE;
+ virtual void blitV(int x, int y, int height, SkAlpha alpha) SK_OVERRIDE;
virtual void blitRect(int x, int y, int width, int height) SK_OVERRIDE;
- virtual void blitAntiH(int x, int y, const SkAlpha antialias[], const int16_t runs[]);
- virtual void blitMask(const SkMask&, const SkIRect&);
+ virtual void blitAntiH(int x, int y, const SkAlpha[], const int16_t[]) SK_OVERRIDE;
+ virtual void blitMask(const SkMask&, const SkIRect&) SK_OVERRIDE;
private:
SkXfermode* fXfermode;
SkPMColor* fBuffer;
SkBlitRow::Proc32 fProc32;
SkBlitRow::Proc32 fProc32Blend;
+ bool fShadeDirectlyIntoDevice;
+ bool fConstInY;
// illegal
SkARGB32_Shader_Blitter& operator=(const SkARGB32_Shader_Blitter&);
diff --git a/src/core/SkDevice.cpp b/src/core/SkDevice.cpp
index 7da9a6b442..56b1841240 100644
--- a/src/core/SkDevice.cpp
+++ b/src/core/SkDevice.cpp
@@ -44,7 +44,7 @@ SkDevice::SkDevice(SkBitmap::Config config, int width, int height, bool isOpaque
fBitmap.allocPixels();
fBitmap.setIsOpaque(isOpaque);
if (!isOpaque) {
- fBitmap.eraseColor(0);
+ fBitmap.eraseColor(SK_ColorTRANSPARENT);
}
}
diff --git a/src/core/SkDraw.cpp b/src/core/SkDraw.cpp
index 885a810836..281c69c017 100644
--- a/src/core/SkDraw.cpp
+++ b/src/core/SkDraw.cpp
@@ -1,4 +1,3 @@
-
/*
* Copyright 2006 The Android Open Source Project
*
@@ -318,8 +317,8 @@ static void bw_pt_rect_hair_proc(const PtProcRec& rec, const SkPoint devPts[],
const SkIRect& r = rec.fClip->getBounds();
for (int i = 0; i < count; i++) {
- int x = SkScalarFloor(devPts[i].fX);
- int y = SkScalarFloor(devPts[i].fY);
+ int x = SkScalarFloorToInt(devPts[i].fX);
+ int y = SkScalarFloorToInt(devPts[i].fY);
if (r.contains(x, y)) {
blitter->blitH(x, y, 1);
}
@@ -339,15 +338,35 @@ static void bw_pt_rect_16_hair_proc(const PtProcRec& rec,
int rb = bitmap->rowBytes();
for (int i = 0; i < count; i++) {
- int x = SkScalarFloor(devPts[i].fX);
- int y = SkScalarFloor(devPts[i].fY);
+ int x = SkScalarFloorToInt(devPts[i].fX);
+ int y = SkScalarFloorToInt(devPts[i].fY);
if (r.contains(x, y)) {
-// *bitmap->getAddr16(x, y) = SkToU16(value);
((uint16_t*)((char*)addr + y * rb))[x] = SkToU16(value);
}
}
}
+static void bw_pt_rect_32_hair_proc(const PtProcRec& rec,
+ const SkPoint devPts[], int count,
+ SkBlitter* blitter) {
+ SkASSERT(rec.fRC->isRect());
+ const SkIRect& r = rec.fRC->getBounds();
+ uint32_t value;
+ const SkBitmap* bitmap = blitter->justAnOpaqueColor(&value);
+ SkASSERT(bitmap);
+
+ SkPMColor* addr = bitmap->getAddr32(0, 0);
+ int rb = bitmap->rowBytes();
+
+ for (int i = 0; i < count; i++) {
+ int x = SkScalarFloorToInt(devPts[i].fX);
+ int y = SkScalarFloorToInt(devPts[i].fY);
+ if (r.contains(x, y)) {
+ ((SkPMColor*)((char*)addr + y * rb))[x] = value;
+ }
+ }
+}
+
static void bw_pt_hair_proc(const PtProcRec& rec, const SkPoint devPts[],
int count, SkBlitter* blitter) {
for (int i = 0; i < count; i++) {
@@ -437,7 +456,7 @@ bool PtProcRec::init(SkCanvas::PointMode mode, const SkPaint& paint,
fPaint = &paint;
fClip = NULL;
fRC = rc;
- fRadius = SK_Fixed1 >> 1;
+ fRadius = SK_FixedHalf;
return true;
}
if (paint.getStrokeCap() != SkPaint::kRound_Cap &&
@@ -479,19 +498,25 @@ PtProcRec::Proc PtProcRec::chooseProc(SkBlitter** blitterPtr) {
SkASSERT(2 == SkCanvas::kPolygon_PointMode);
SkASSERT((unsigned)fMode <= (unsigned)SkCanvas::kPolygon_PointMode);
- // first check for hairlines
- if (0 == fPaint->getStrokeWidth()) {
- if (fPaint->isAntiAlias()) {
+ if (fPaint->isAntiAlias()) {
+ if (0 == fPaint->getStrokeWidth()) {
static const Proc gAAProcs[] = {
aa_square_proc, aa_line_hair_proc, aa_poly_hair_proc
};
proc = gAAProcs[fMode];
- } else {
+ } else if (fPaint->getStrokeCap() != SkPaint::kRound_Cap) {
+ SkASSERT(SkCanvas::kPoints_PointMode == fMode);
+ proc = aa_square_proc;
+ }
+ } else { // BW
+ if (fRadius <= SK_FixedHalf) { // small radii and hairline
if (SkCanvas::kPoints_PointMode == fMode && fClip->isRect()) {
uint32_t value;
const SkBitmap* bm = blitter->justAnOpaqueColor(&value);
- if (bm && bm->config() == SkBitmap::kRGB_565_Config) {
+ if (bm && SkBitmap::kRGB_565_Config == bm->config()) {
proc = bw_pt_rect_16_hair_proc;
+ } else if (bm && SkBitmap::kARGB_8888_Config == bm->config()) {
+ proc = bw_pt_rect_32_hair_proc;
} else {
proc = bw_pt_rect_hair_proc;
}
@@ -501,11 +526,6 @@ PtProcRec::Proc PtProcRec::chooseProc(SkBlitter** blitterPtr) {
};
proc = gBWProcs[fMode];
}
- }
- } else if (fPaint->getStrokeCap() != SkPaint::kRound_Cap) {
- SkASSERT(SkCanvas::kPoints_PointMode == fMode);
- if (fPaint->isAntiAlias()) {
- proc = aa_square_proc;
} else {
proc = bw_square_proc;
}
@@ -635,6 +655,89 @@ void SkDraw::drawPoints(SkCanvas::PointMode mode, size_t count,
break;
}
case SkCanvas::kLines_PointMode:
+#ifndef SK_DISABLE_DASHING_OPTIMIZATION
+ if (2 == count && NULL != paint.getPathEffect()) {
+ // most likely a dashed line - see if it is one of the ones
+ // we can accelerate
+ SkStrokeRec rec(paint);
+ SkPathEffect::PointData pointData;
+
+ SkPath path;
+ path.moveTo(pts[0]);
+ path.lineTo(pts[1]);
+
+ if (paint.getPathEffect()->asPoints(&pointData, path, rec, *fMatrix)) {
+ // 'asPoints' managed to find some fast path
+
+ SkPaint newP(paint);
+ newP.setPathEffect(NULL);
+ newP.setStyle(SkPaint::kFill_Style);
+
+ if (!pointData.fFirst.isEmpty()) {
+ if (fDevice) {
+ fDevice->drawPath(*this, pointData.fFirst, newP);
+ } else {
+ this->drawPath(pointData.fFirst, newP);
+ }
+ }
+
+ if (!pointData.fLast.isEmpty()) {
+ if (fDevice) {
+ fDevice->drawPath(*this, pointData.fLast, newP);
+ } else {
+ this->drawPath(pointData.fLast, newP);
+ }
+ }
+
+ if (pointData.fSize.fX == pointData.fSize.fY) {
+ // The rest of the dashed line can just be drawn as points
+ SkASSERT(pointData.fSize.fX == SkScalarHalf(newP.getStrokeWidth()));
+
+ if (SkPathEffect::PointData::kCircles_PointFlag & pointData.fFlags) {
+ newP.setStrokeCap(SkPaint::kRound_Cap);
+ } else {
+ newP.setStrokeCap(SkPaint::kButt_Cap);
+ }
+
+ if (fDevice) {
+ fDevice->drawPoints(*this,
+ SkCanvas::kPoints_PointMode,
+ pointData.fNumPoints,
+ pointData.fPoints,
+ newP);
+ } else {
+ this->drawPoints(SkCanvas::kPoints_PointMode,
+ pointData.fNumPoints,
+ pointData.fPoints,
+ newP,
+ forceUseDevice);
+ }
+ break;
+ } else {
+ // The rest of the dashed line must be drawn as rects
+ SkASSERT(!(SkPathEffect::PointData::kCircles_PointFlag &
+ pointData.fFlags));
+
+ SkRect r;
+
+ for (int i = 0; i < pointData.fNumPoints; ++i) {
+ r.set(pointData.fPoints[i].fX - pointData.fSize.fX,
+ pointData.fPoints[i].fY - pointData.fSize.fY,
+ pointData.fPoints[i].fX + pointData.fSize.fX,
+ pointData.fPoints[i].fY + pointData.fSize.fY);
+ if (fDevice) {
+ fDevice->drawRect(*this, r, newP);
+ } else {
+ this->drawRect(r, newP);
+ }
+ }
+ }
+
+ break;
+ }
+ }
+#endif // DISABLE_DASHING_OPTIMIZATION
+ // couldn't take fast path so fall through!
case SkCanvas::kPolygon_PointMode: {
count -= 1;
SkPath path;
@@ -727,12 +830,6 @@ void SkDraw::drawRect(const SkRect& rect, const SkPaint& paint) const {
SkPoint strokeSize;
RectType rtype = ComputeRectType(paint, *fMatrix, &strokeSize);
-#ifdef SK_DISABLE_FAST_AA_STROKE_RECT
- if (kStroke_RectType == rtype && paint.isAntiAlias()) {
- rtype = kPath_RectType;
- }
-#endif
-
if (kPath_RectType == rtype) {
SkPath tmp;
tmp.addRect(rect);
@@ -1042,6 +1139,11 @@ void SkDraw::drawBitmapAsMask(const SkBitmap& bitmap,
int ix = SkScalarRound(fMatrix->getTranslateX());
int iy = SkScalarRound(fMatrix->getTranslateY());
+ SkAutoLockPixels alp(bitmap);
+ if (!bitmap.readyToDraw()) {
+ return;
+ }
+
SkMask mask;
mask.fBounds.set(ix, iy, ix + bitmap.width(), iy + bitmap.height());
mask.fFormat = SkMask::kA8_Format;
@@ -1172,15 +1274,16 @@ void SkDraw::drawBitmap(const SkBitmap& bitmap, const SkMatrix& prematrix,
}
}
- // only lock the pixels if we passed the clip and bounder tests
- SkAutoLockPixels alp(bitmap);
- // after the lock, check if we are valid
- if (!bitmap.readyToDraw()) {
- return;
- }
-
if (bitmap.getConfig() != SkBitmap::kA8_Config &&
just_translate(matrix, bitmap)) {
+ //
+ // It is safe to call lock pixels now, since we know the matrix is
+ // (more or less) identity.
+ //
+ SkAutoLockPixels alp(bitmap);
+ if (!bitmap.readyToDraw()) {
+ return;
+ }
int ix = SkScalarRound(matrix.getTranslateX());
int iy = SkScalarRound(matrix.getTranslateY());
if (clipHandlesSprite(*fRC, ix, iy, bitmap)) {
@@ -2223,7 +2326,7 @@ public:
bool setup(const SkPoint pts[], const SkColor colors[], int, int, int);
- virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
+ virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkTriColorShader)
@@ -2350,7 +2453,7 @@ void SkDraw::drawVertices(SkCanvas::VertexMode vmode, int count,
if (NULL != colors) {
if (NULL == textures) {
// just colors (no texture)
- p.setShader(&triShader);
+ shader = p.setShader(&triShader);
} else {
// colors * texture
SkASSERT(shader);
@@ -2365,32 +2468,36 @@ void SkDraw::drawVertices(SkCanvas::VertexMode vmode, int count,
if (releaseMode) {
xmode->unref();
}
+ shader = compose;
}
}
SkAutoBlitterChoose blitter(*fBitmap, *fMatrix, p);
+ // important that we abort early, as below we may manipulate the shader
+ // and that is only valid if the shader returned true from setContext.
+ // If it returned false, then our blitter will be the NullBlitter.
+ if (blitter->isNullBlitter()) {
+ return;
+ }
+
// setup our state and function pointer for iterating triangles
VertState state(count, indices, indexCount);
VertState::Proc vertProc = state.chooseProc(vmode);
if (NULL != textures || NULL != colors) {
- SkMatrix localM, tempM;
- bool hasLocalM = shader && shader->getLocalMatrix(&localM);
-
- if (NULL != colors) {
- if (!triShader.setContext(*fBitmap, p, *fMatrix)) {
- colors = NULL;
- }
+ SkMatrix tempM;
+ SkMatrix savedLocalM;
+ if (shader) {
+ savedLocalM = shader->getLocalMatrix();
}
while (vertProc(&state)) {
if (NULL != textures) {
if (texture_to_matrix(state, vertices, textures, &tempM)) {
- if (hasLocalM) {
- tempM.postConcat(localM);
- }
+ tempM.postConcat(savedLocalM);
shader->setLocalMatrix(tempM);
// need to recal setContext since we changed the local matrix
+ shader->endContext();
if (!shader->setContext(*fBitmap, p, *fMatrix)) {
continue;
}
@@ -2410,11 +2517,7 @@ void SkDraw::drawVertices(SkCanvas::VertexMode vmode, int count,
}
// now restore the shader's original local matrix
if (NULL != shader) {
- if (hasLocalM) {
- shader->setLocalMatrix(localM);
- } else {
- shader->resetLocalMatrix();
- }
+ shader->setLocalMatrix(savedLocalM);
}
} else {
// no colors[] and no texture
@@ -2547,7 +2650,7 @@ void SkBounder::commit() {
#include "SkBlitter.h"
static bool compute_bounds(const SkPath& devPath, const SkIRect* clipBounds,
- SkMaskFilter* filter, const SkMatrix* filterMatrix,
+ const SkMaskFilter* filter, const SkMatrix* filterMatrix,
SkIRect* bounds) {
if (devPath.isEmpty()) {
return false;
@@ -2621,7 +2724,7 @@ static void draw_into_mask(const SkMask& mask, const SkPath& devPath,
}
bool SkDraw::DrawToMask(const SkPath& devPath, const SkIRect* clipBounds,
- SkMaskFilter* filter, const SkMatrix* filterMatrix,
+ const SkMaskFilter* filter, const SkMatrix* filterMatrix,
SkMask* mask, SkMask::CreateMode mode,
SkPaint::Style style) {
if (SkMask::kJustRenderImage_CreateMode != mode) {
diff --git a/src/core/SkEdge.cpp b/src/core/SkEdge.cpp
index fff3dbcc6a..e646c4765f 100644
--- a/src/core/SkEdge.cpp
+++ b/src/core/SkEdge.cpp
@@ -72,8 +72,9 @@ int SkEdge::setLine(const SkPoint& p0, const SkPoint& p1, const SkIRect* clip,
}
SkFixed slope = SkFDot6Div(x1 - x0, y1 - y0);
+ const int dy = SkEdge_Compute_DY(top, y0);
- fX = SkFDot6ToFixed(x0 + SkFixedMul(slope, (32 - y0) & 63)); // + SK_Fixed1/2
+ fX = SkFDot6ToFixed(x0 + SkFixedMul(slope, dy)); // + SK_Fixed1/2
fDX = slope;
fFirstY = top;
fLastY = bot - 1;
@@ -112,8 +113,9 @@ int SkEdge::updateLine(SkFixed x0, SkFixed y0, SkFixed x1, SkFixed y1)
x1 >>= 10;
SkFixed slope = SkFDot6Div(x1 - x0, y1 - y0);
+ const int dy = SkEdge_Compute_DY(top, y0);
- fX = SkFDot6ToFixed(x0 + SkFixedMul(slope, (32 - y0) & 63)); // + SK_Fixed1/2
+ fX = SkFDot6ToFixed(x0 + SkFixedMul(slope, dy)); // + SK_Fixed1/2
fDX = slope;
fFirstY = top;
fLastY = bot - 1;
diff --git a/src/core/SkEdge.h b/src/core/SkEdge.h
index 53678621c3..c36ba246ce 100644
--- a/src/core/SkEdge.h
+++ b/src/core/SkEdge.h
@@ -14,6 +14,15 @@
#include "SkFDot6.h"
#include "SkMath.h"
+//#ifdef SK_IGNORE_SETLINE_FIX
+#if 1
+ #define SkEdge_Compute_DY(top, y0) ((32 - (y0)) & 63)
+#else
+ // This is correct, as it favors the lower-pixel when y0 is on a 1/2 pixel
+ // boundary, returning 64 instead of the old code, which returns 0.
+ #define SkEdge_Compute_DY(top, y0) ((top << 6) + 32 - (y0))
+#endif
+
struct SkEdge {
enum Type {
kLine_Type,
@@ -118,8 +127,9 @@ int SkEdge::setLine(const SkPoint& p0, const SkPoint& p1, int shift) {
}
SkFixed slope = SkFDot6Div(x1 - x0, y1 - y0);
+ const int dy = SkEdge_Compute_DY(top, y0);
- fX = SkFDot6ToFixed(x0 + SkFixedMul(slope, (32 - y0) & 63)); // + SK_Fixed1/2
+ fX = SkFDot6ToFixed(x0 + SkFixedMul(slope, dy)); // + SK_Fixed1/2
fDX = slope;
fFirstY = top;
fLastY = bot - 1;
diff --git a/src/core/SkFilterShader.h b/src/core/SkFilterShader.h
index f637584718..ded3125adc 100644
--- a/src/core/SkFilterShader.h
+++ b/src/core/SkFilterShader.h
@@ -17,14 +17,12 @@ public:
SkFilterShader(SkShader* shader, SkColorFilter* filter);
virtual ~SkFilterShader();
- // override
- virtual uint32_t getFlags();
- virtual bool setContext(const SkBitmap& device, const SkPaint& paint,
- const SkMatrix& matrix);
- virtual void shadeSpan(int x, int y, SkPMColor result[], int count);
- virtual void shadeSpan16(int x, int y, uint16_t result[], int count);
- virtual void beginSession();
- virtual void endSession();
+ virtual uint32_t getFlags() SK_OVERRIDE;
+ virtual bool setContext(const SkBitmap&, const SkPaint&,
+ const SkMatrix&) SK_OVERRIDE;
+ virtual void endContext() SK_OVERRIDE;
+ virtual void shadeSpan(int x, int y, SkPMColor[], int count) SK_OVERRIDE;
+ virtual void shadeSpan16(int x, int y, uint16_t[], int count) SK_OVERRIDE;
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkFilterShader)
diff --git a/src/core/SkGeometry.cpp b/src/core/SkGeometry.cpp
index 1c1a4f1045..137cd795c1 100644
--- a/src/core/SkGeometry.cpp
+++ b/src/core/SkGeometry.cpp
@@ -1254,28 +1254,52 @@ static bool quad_pt2OffCurve(const SkPoint quad[3], SkScalar x, SkScalar y, SkPo
return false;
}
+#ifdef SK_SCALAR_IS_FLOAT
+// Due to floating point issues (i.e., 1.0f - SK_ScalarRoot2Over2 !=
+// SK_ScalarRoot2Over2 - SK_ScalarTanPIOver8) a cruder root2over2
+// approximation is required to make the quad circle points convex. The
+// root of the problem is that with the root2over2 value in SkScalar.h
+// the arcs really are ever so slightly concave. Some alternative fixes
+// to this problem (besides just arbitrarily pushing out the mid-point as
+// is done here) are:
+// Adjust all the points (not just the middle) to both better approximate
+// the curve and remain convex
+// Switch over to using cubics rather then quads
+// Use a different method to create the mid-point (e.g., compute
+// the two side points, average them, then move it out as needed
+#ifdef SK_REDEFINE_ROOT2OVER2_TO_MAKE_ARCTOS_CONVEX
+ #define SK_ScalarRoot2Over2_QuadCircle 0.7072f
+#else
+ #define SK_ScalarRoot2Over2_QuadCircle SK_ScalarRoot2Over2
+#endif
+
+#else
+ #define SK_ScalarRoot2Over2_QuadCircle SK_FixedRoot2Over2
+#endif
+
+
static const SkPoint gQuadCirclePts[kSkBuildQuadArcStorage] = {
- { SK_Scalar1, 0 },
- { SK_Scalar1, SK_ScalarTanPIOver8 },
- { SK_ScalarRoot2Over2, SK_ScalarRoot2Over2 },
- { SK_ScalarTanPIOver8, SK_Scalar1 },
-
- { 0, SK_Scalar1 },
- { -SK_ScalarTanPIOver8, SK_Scalar1 },
- { -SK_ScalarRoot2Over2, SK_ScalarRoot2Over2 },
- { -SK_Scalar1, SK_ScalarTanPIOver8 },
-
- { -SK_Scalar1, 0 },
- { -SK_Scalar1, -SK_ScalarTanPIOver8 },
- { -SK_ScalarRoot2Over2, -SK_ScalarRoot2Over2 },
- { -SK_ScalarTanPIOver8, -SK_Scalar1 },
-
- { 0, -SK_Scalar1 },
- { SK_ScalarTanPIOver8, -SK_Scalar1 },
- { SK_ScalarRoot2Over2, -SK_ScalarRoot2Over2 },
- { SK_Scalar1, -SK_ScalarTanPIOver8 },
-
- { SK_Scalar1, 0 }
+ { SK_Scalar1, 0 },
+ { SK_Scalar1, SK_ScalarTanPIOver8 },
+ { SK_ScalarRoot2Over2_QuadCircle, SK_ScalarRoot2Over2_QuadCircle },
+ { SK_ScalarTanPIOver8, SK_Scalar1 },
+
+ { 0, SK_Scalar1 },
+ { -SK_ScalarTanPIOver8, SK_Scalar1 },
+ { -SK_ScalarRoot2Over2_QuadCircle, SK_ScalarRoot2Over2_QuadCircle },
+ { -SK_Scalar1, SK_ScalarTanPIOver8 },
+
+ { -SK_Scalar1, 0 },
+ { -SK_Scalar1, -SK_ScalarTanPIOver8 },
+ { -SK_ScalarRoot2Over2_QuadCircle, -SK_ScalarRoot2Over2_QuadCircle },
+ { -SK_ScalarTanPIOver8, -SK_Scalar1 },
+
+ { 0, -SK_Scalar1 },
+ { SK_ScalarTanPIOver8, -SK_Scalar1 },
+ { SK_ScalarRoot2Over2_QuadCircle, -SK_ScalarRoot2Over2_QuadCircle },
+ { SK_Scalar1, -SK_ScalarTanPIOver8 },
+
+ { SK_Scalar1, 0 }
};
int SkBuildQuadArc(const SkVector& uStart, const SkVector& uStop,
diff --git a/src/core/SkGlyphCache.cpp b/src/core/SkGlyphCache.cpp
index 5904edd3c0..b878df8b77 100644
--- a/src/core/SkGlyphCache.cpp
+++ b/src/core/SkGlyphCache.cpp
@@ -599,7 +599,7 @@ void SkGlyphCache::AttachCache(SkGlyphCache* cache) {
// if we have a fixed budget for our cache, do a purge here
{
size_t allocated = globals.fTotalMemoryUsed + cache->fMemoryUsed;
- size_t budgeted = SkGraphics::GetFontCacheLimit();
+ size_t budgeted = globals.getFontCacheLimit();
if (allocated > budgeted) {
(void)InternalFreeCache(&globals, allocated - budgeted);
}
@@ -693,6 +693,7 @@ size_t SkGlyphCache::InternalFreeCache(SkGlyphCache_Globals* globals,
#ifdef SK_DEBUG
void SkGlyphCache::validate() const {
+#ifdef SK_DEBUG_GLYPH_CACHE
int count = fGlyphArray.count();
for (int i = 0; i < count; i++) {
const SkGlyph* glyph = fGlyphArray[i];
@@ -702,6 +703,7 @@ void SkGlyphCache::validate() const {
SkASSERT(fImageAlloc.contains(glyph->fImage));
}
}
+#endif
}
#endif
diff --git a/src/core/SkImageFilter.cpp b/src/core/SkImageFilter.cpp
index 45ad024d45..05e682c63c 100644
--- a/src/core/SkImageFilter.cpp
+++ b/src/core/SkImageFilter.cpp
@@ -10,7 +10,6 @@
#include "SkBitmap.h"
#include "SkFlattenableBuffers.h"
#include "SkRect.h"
-#include "stdarg.h"
SK_DEFINE_INST_COUNT(SkImageFilter)
@@ -22,15 +21,18 @@ SkImageFilter::SkImageFilter(int inputCount, SkImageFilter** inputs)
}
}
-SkImageFilter::SkImageFilter(int inputCount, ...)
- : fInputCount(inputCount), fInputs(new SkImageFilter*[inputCount]) {
- va_list ap;
- va_start(ap, inputCount);
- for (int i = 0; i < inputCount; ++i) {
- fInputs[i] = va_arg(ap, SkImageFilter*);
- SkSafeRef(fInputs[i]);
- }
- va_end(ap);
+SkImageFilter::SkImageFilter(SkImageFilter* input)
+ : fInputCount(1), fInputs(new SkImageFilter*[1]) {
+ fInputs[0] = input;
+ SkSafeRef(fInputs[0]);
+}
+
+SkImageFilter::SkImageFilter(SkImageFilter* input1, SkImageFilter* input2)
+ : fInputCount(2), fInputs(new SkImageFilter*[2]) {
+ fInputs[0] = input1;
+ fInputs[1] = input2;
+ SkSafeRef(fInputs[0]);
+ SkSafeRef(fInputs[1]);
}
SkImageFilter::~SkImageFilter() {
@@ -115,7 +117,7 @@ bool SkImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
return true;
}
-bool SkImageFilter::asNewCustomStage(GrCustomStage**, GrTexture*) const {
+bool SkImageFilter::asNewEffect(GrEffect**, GrTexture*) const {
return false;
}
diff --git a/src/core/SkMaskFilter.cpp b/src/core/SkMaskFilter.cpp
index f27ebd1e14..5de76c76dd 100644
--- a/src/core/SkMaskFilter.cpp
+++ b/src/core/SkMaskFilter.cpp
@@ -13,16 +13,214 @@
#include "SkDraw.h"
#include "SkRasterClip.h"
+
SK_DEFINE_INST_COUNT(SkMaskFilter)
bool SkMaskFilter::filterMask(SkMask*, const SkMask&, const SkMatrix&,
- SkIPoint*) {
+ SkIPoint*) const {
return false;
}
+static void extractMaskSubset(const SkMask& src, SkMask* dst) {
+ SkASSERT(src.fBounds.contains(dst->fBounds));
+
+ const int dx = dst->fBounds.left() - src.fBounds.left();
+ const int dy = dst->fBounds.top() - src.fBounds.top();
+ dst->fImage = src.fImage + dy * src.fRowBytes + dx;
+ dst->fRowBytes = src.fRowBytes;
+ dst->fFormat = src.fFormat;
+}
+
+static void blitClippedMask(SkBlitter* blitter, const SkMask& mask,
+ const SkIRect& bounds, const SkIRect& clipR) {
+ SkIRect r;
+ if (r.intersect(bounds, clipR)) {
+ blitter->blitMask(mask, r);
+ }
+}
+
+static void blitClippedRect(SkBlitter* blitter, const SkIRect& rect, const SkIRect& clipR) {
+ SkIRect r;
+ if (r.intersect(rect, clipR)) {
+ blitter->blitRect(r.left(), r.top(), r.width(), r.height());
+ }
+}
+
+#if 0
+static void dump(const SkMask& mask) {
+ for (int y = mask.fBounds.top(); y < mask.fBounds.bottom(); ++y) {
+ for (int x = mask.fBounds.left(); x < mask.fBounds.right(); ++x) {
+ SkDebugf("%02X", *mask.getAddr8(x, y));
+ }
+ SkDebugf("\n");
+ }
+ SkDebugf("\n");
+}
+#endif
+
+static void draw_nine_clipped(const SkMask& mask, const SkIRect& outerR,
+ const SkIPoint& center, bool fillCenter,
+ const SkIRect& clipR, SkBlitter* blitter) {
+ int cx = center.x();
+ int cy = center.y();
+ SkMask m;
+
+ // top-left
+ m.fBounds = mask.fBounds;
+ m.fBounds.fRight = cx;
+ m.fBounds.fBottom = cy;
+ extractMaskSubset(mask, &m);
+ m.fBounds.offsetTo(outerR.left(), outerR.top());
+ blitClippedMask(blitter, m, m.fBounds, clipR);
+
+ // top-right
+ m.fBounds = mask.fBounds;
+ m.fBounds.fLeft = cx + 1;
+ m.fBounds.fBottom = cy;
+ extractMaskSubset(mask, &m);
+ m.fBounds.offsetTo(outerR.right() - m.fBounds.width(), outerR.top());
+ blitClippedMask(blitter, m, m.fBounds, clipR);
+
+ // bottom-left
+ m.fBounds = mask.fBounds;
+ m.fBounds.fRight = cx;
+ m.fBounds.fTop = cy + 1;
+ extractMaskSubset(mask, &m);
+ m.fBounds.offsetTo(outerR.left(), outerR.bottom() - m.fBounds.height());
+ blitClippedMask(blitter, m, m.fBounds, clipR);
+
+ // bottom-right
+ m.fBounds = mask.fBounds;
+ m.fBounds.fLeft = cx + 1;
+ m.fBounds.fTop = cy + 1;
+ extractMaskSubset(mask, &m);
+ m.fBounds.offsetTo(outerR.right() - m.fBounds.width(),
+ outerR.bottom() - m.fBounds.height());
+ blitClippedMask(blitter, m, m.fBounds, clipR);
+
+ SkIRect innerR;
+ innerR.set(outerR.left() + cx - mask.fBounds.left(),
+ outerR.top() + cy - mask.fBounds.top(),
+ outerR.right() + (cx + 1 - mask.fBounds.right()),
+ outerR.bottom() + (cy + 1 - mask.fBounds.bottom()));
+ if (fillCenter) {
+ blitClippedRect(blitter, innerR, clipR);
+ }
+
+ const int innerW = innerR.width();
+ size_t storageSize = (innerW + 1) * (sizeof(int16_t) + sizeof(uint8_t));
+ SkAutoSMalloc<4*1024> storage(storageSize);
+ int16_t* runs = (int16_t*)storage.get();
+ uint8_t* alpha = (uint8_t*)(runs + innerW + 1);
+
+ SkIRect r;
+ // top
+ r.set(innerR.left(), outerR.top(), innerR.right(), innerR.top());
+ if (r.intersect(clipR)) {
+ int startY = SkMax32(0, r.top() - outerR.top());
+ int stopY = startY + r.height();
+ int width = r.width();
+ for (int y = startY; y < stopY; ++y) {
+ runs[0] = width;
+ runs[width] = 0;
+ alpha[0] = *mask.getAddr8(cx, mask.fBounds.top() + y);
+ blitter->blitAntiH(r.left(), outerR.top() + y, alpha, runs);
+ }
+ }
+ // bottom
+ r.set(innerR.left(), innerR.bottom(), innerR.right(), outerR.bottom());
+ if (r.intersect(clipR)) {
+ int startY = outerR.bottom() - r.bottom();
+ int stopY = startY + r.height();
+ int width = r.width();
+ for (int y = startY; y < stopY; ++y) {
+ runs[0] = width;
+ runs[width] = 0;
+ alpha[0] = *mask.getAddr8(cx, mask.fBounds.bottom() - y - 1);
+ blitter->blitAntiH(r.left(), outerR.bottom() - y - 1, alpha, runs);
+ }
+ }
+ // left
+ r.set(outerR.left(), innerR.top(), innerR.left(), innerR.bottom());
+ if (r.intersect(clipR)) {
+ int startX = r.left() - outerR.left();
+ int stopX = startX + r.width();
+ int height = r.height();
+ for (int x = startX; x < stopX; ++x) {
+ blitter->blitV(outerR.left() + x, r.top(), height,
+ *mask.getAddr8(mask.fBounds.left() + x, mask.fBounds.top() + cy));
+ }
+ }
+ // right
+ r.set(innerR.right(), innerR.top(), outerR.right(), innerR.bottom());
+ if (r.intersect(clipR)) {
+ int startX = outerR.right() - r.right();
+ int stopX = startX + r.width();
+ int height = r.height();
+ for (int x = startX; x < stopX; ++x) {
+ blitter->blitV(outerR.right() - x - 1, r.top(), height,
+ *mask.getAddr8(mask.fBounds.right() - x - 1, mask.fBounds.top() + cy));
+ }
+ }
+}
+
+static void draw_nine(const SkMask& mask, const SkIRect& outerR,
+ const SkIPoint& center, bool fillCenter,
+ const SkRasterClip& clip, SkBounder* bounder,
+ SkBlitter* blitter) {
+ // if we get here, we need to (possibly) resolve the clip and blitter
+ SkAAClipBlitterWrapper wrapper(clip, blitter);
+ blitter = wrapper.getBlitter();
+
+ SkRegion::Cliperator clipper(wrapper.getRgn(), outerR);
+
+ if (!clipper.done() && (!bounder || bounder->doIRect(outerR))) {
+ const SkIRect& cr = clipper.rect();
+ do {
+ draw_nine_clipped(mask, outerR, center, fillCenter, cr, blitter);
+ clipper.next();
+ } while (!clipper.done());
+ }
+}
+
+static int countNestedRects(const SkPath& path, SkRect rects[2]) {
+ if (path.isNestedRects(rects)) {
+ return 2;
+ }
+ return path.isRect(&rects[0]);
+}
+
bool SkMaskFilter::filterPath(const SkPath& devPath, const SkMatrix& matrix,
const SkRasterClip& clip, SkBounder* bounder,
- SkBlitter* blitter, SkPaint::Style style) {
+ SkBlitter* blitter, SkPaint::Style style) const {
+ SkRect rects[2];
+ int rectCount = 0;
+ if (SkPaint::kFill_Style == style) {
+ rectCount = countNestedRects(devPath, rects);
+ }
+ if (rectCount > 0) {
+ NinePatch patch;
+
+ patch.fMask.fImage = NULL;
+ switch (this->filterRectsToNine(rects, rectCount, matrix,
+ clip.getBounds(), &patch)) {
+ case kFalse_FilterReturn:
+ SkASSERT(NULL == patch.fMask.fImage);
+ return false;
+
+ case kTrue_FilterReturn:
+ draw_nine(patch.fMask, patch.fOuterRect, patch.fCenter,
+ 1 == rectCount, clip, bounder, blitter);
+ SkMask::FreeImage(patch.fMask.fImage);
+ return true;
+
+ case kUnimplemented_FilterReturn:
+ SkASSERT(NULL == patch.fMask.fImage);
+ // fall through
+ break;
+ }
+ }
+
SkMask srcM, dstM;
if (!SkDraw::DrawToMask(devPath, &clip.getBounds(), this, &matrix, &srcM,
@@ -54,11 +252,17 @@ bool SkMaskFilter::filterPath(const SkPath& devPath, const SkMatrix& matrix,
return true;
}
+SkMaskFilter::FilterReturn
+SkMaskFilter::filterRectsToNine(const SkRect[], int count, const SkMatrix&,
+ const SkIRect& clipBounds, NinePatch*) const {
+ return kUnimplemented_FilterReturn;
+}
+
SkMaskFilter::BlurType SkMaskFilter::asABlur(BlurInfo*) const {
return kNone_BlurType;
}
-void SkMaskFilter::computeFastBounds(const SkRect& src, SkRect* dst) {
+void SkMaskFilter::computeFastBounds(const SkRect& src, SkRect* dst) const {
SkMask srcM, dstM;
srcM.fImage = NULL;
diff --git a/src/core/SkMaskGamma.h b/src/core/SkMaskGamma.h
index a092b209ee..ba3be64dcd 100644
--- a/src/core/SkMaskGamma.h
+++ b/src/core/SkMaskGamma.h
@@ -95,8 +95,8 @@ template <int R_LUM_BITS, int G_LUM_BITS, int B_LUM_BITS> class SkTMaskGamma : p
public:
SK_DECLARE_INST_COUNT_TEMPLATE(SkTMaskGamma)
- SkTMaskGamma() : fIsLinear(true) {
- }
+ /** Creates a linear SkTMaskGamma. */
+ SkTMaskGamma() : fIsLinear(true) { }
/**
* Creates tables to convert linear alpha values to gamma correcting alpha
@@ -110,8 +110,8 @@ public:
SkTMaskGamma(SkScalar contrast, SkScalar paintGamma, SkScalar deviceGamma) : fIsLinear(false) {
const SkColorSpaceLuminance& paintConvert = SkColorSpaceLuminance::Fetch(paintGamma);
const SkColorSpaceLuminance& deviceConvert = SkColorSpaceLuminance::Fetch(deviceGamma);
- for (U8CPU i = 0; i < (1 << kLuminanceBits_Max); ++i) {
- U8CPU lum = sk_t_scale255<kLuminanceBits_Max>(i);
+ for (U8CPU i = 0; i < (1 << MAX_LUM_BITS); ++i) {
+ U8CPU lum = sk_t_scale255<MAX_LUM_BITS>(i);
SkTMaskGamma_build_correcting_lut(fGammaTables[i], lum, contrast,
paintConvert, paintGamma,
deviceConvert, deviceGamma);
@@ -119,11 +119,11 @@ public:
}
/** Given a color, returns the closest cannonical color. */
- static SkColor cannonicalColor(SkColor color) {
+ static SkColor CannonicalColor(SkColor color) {
return SkColorSetRGB(
- sk_t_scale255<kLuminanceBits_R>(SkColorGetR(color) >> (8 - kLuminanceBits_R)),
- sk_t_scale255<kLuminanceBits_G>(SkColorGetG(color) >> (8 - kLuminanceBits_G)),
- sk_t_scale255<kLuminanceBits_B>(SkColorGetB(color) >> (8 - kLuminanceBits_B)));
+ sk_t_scale255<R_LUM_BITS>(SkColorGetR(color) >> (8 - R_LUM_BITS)),
+ sk_t_scale255<G_LUM_BITS>(SkColorGetG(color) >> (8 - G_LUM_BITS)),
+ sk_t_scale255<B_LUM_BITS>(SkColorGetB(color) >> (8 - B_LUM_BITS)));
}
/** The type of the mask pre-blend which will be returned from preBlend(SkColor). */
@@ -134,18 +134,13 @@ public:
* values into gamma correcting alpha values when drawing the given color
* through the mask. The destination color will be approximated.
*/
- PreBlend preBlend(SkColor color);
+ PreBlend preBlend(SkColor color) const;
private:
- enum LuminanceBits {
- kLuminanceBits_R = R_LUM_BITS,
- kLuminanceBits_G = G_LUM_BITS,
- kLuminanceBits_B = B_LUM_BITS,
- kLuminanceBits_Max = B_LUM_BITS > (R_LUM_BITS > G_LUM_BITS ? R_LUM_BITS : G_LUM_BITS)
- ? B_LUM_BITS
- : (R_LUM_BITS > G_LUM_BITS ? R_LUM_BITS : G_LUM_BITS)
- };
- uint8_t fGammaTables[1 << kLuminanceBits_Max][256];
+ static const int MAX_LUM_BITS =
+ B_LUM_BITS > (R_LUM_BITS > G_LUM_BITS ? R_LUM_BITS : G_LUM_BITS)
+ ? B_LUM_BITS : (R_LUM_BITS > G_LUM_BITS ? R_LUM_BITS : G_LUM_BITS);
+ uint8_t fGammaTables[1 << MAX_LUM_BITS][256];
bool fIsLinear;
typedef SkRefCnt INHERITED;
@@ -163,29 +158,35 @@ SK_DEFINE_INST_COUNT_TEMPLATE(
* value for that channel. This class is immutable.
*
* If fR, fG, or fB is NULL, all of them will be. This indicates that no mask
- * pre blend should be applied.
+ * pre blend should be applied. SkTMaskPreBlend::isApplicable() is provided as
+ * a convenience function to test for the absence of this case.
*/
template <int R_LUM_BITS, int G_LUM_BITS, int B_LUM_BITS> class SkTMaskPreBlend {
private:
- SkTMaskPreBlend(SkTMaskGamma<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>* parent,
- const uint8_t* r,
- const uint8_t* g,
- const uint8_t* b)
- : fParent(parent), fR(r), fG(g), fB(b) {
- SkSafeRef(parent);
- }
- SkAutoTUnref<SkTMaskGamma<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS> > fParent;
+ SkTMaskPreBlend(const SkTMaskGamma<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>* parent,
+ const uint8_t* r, const uint8_t* g, const uint8_t* b)
+ : fParent(SkSafeRef(parent)), fR(r), fG(g), fB(b) { }
+
+ SkAutoTUnref<const SkTMaskGamma<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS> > fParent;
friend class SkTMaskGamma<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>;
public:
+ /** Creates a non applicable SkTMaskPreBlend. */
+ SkTMaskPreBlend() : fParent(), fR(NULL), fG(NULL), fB(NULL) { }
+
/**
* This copy contructor exists for correctness, but should never be called
* when return value optimization is enabled.
*/
SkTMaskPreBlend(const SkTMaskPreBlend<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>& that)
- : fParent(that.fParent.get()), fR(that.fR), fG(that.fG), fB(that.fB) {
- SkSafeRef(fParent.get());
- }
+ : fParent(SkSafeRef(that.fParent.get())), fR(that.fR), fG(that.fG), fB(that.fB) { }
+
~SkTMaskPreBlend() { }
+
+ /** True if this PreBlend should be applied. When false, fR, fG, and fB are NULL. */
+ bool isApplicable() const {
+ return NULL != this->fG;
+ }
+
const uint8_t* fR;
const uint8_t* fG;
const uint8_t* fB;
@@ -193,14 +194,12 @@ public:
template <int R_LUM_BITS, int G_LUM_BITS, int B_LUM_BITS>
SkTMaskPreBlend<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>
-SkTMaskGamma<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>::preBlend(SkColor color) {
- return fIsLinear ? SkTMaskPreBlend<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>(
- NULL, NULL, NULL, NULL)
- : SkTMaskPreBlend<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>(
- this,
- fGammaTables[SkColorGetR(color) >> (8 - kLuminanceBits_Max)],
- fGammaTables[SkColorGetG(color) >> (8 - kLuminanceBits_Max)],
- fGammaTables[SkColorGetB(color) >> (8 - kLuminanceBits_Max)]);
+SkTMaskGamma<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>::preBlend(SkColor color) const {
+ return fIsLinear ? SkTMaskPreBlend<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>()
+ : SkTMaskPreBlend<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>(this,
+ fGammaTables[SkColorGetR(color) >> (8 - MAX_LUM_BITS)],
+ fGammaTables[SkColorGetG(color) >> (8 - MAX_LUM_BITS)],
+ fGammaTables[SkColorGetB(color) >> (8 - MAX_LUM_BITS)]);
}
///@{
diff --git a/src/core/SkMatrix.cpp b/src/core/SkMatrix.cpp
index 4cd27e42ee..8dee0cb229 100644
--- a/src/core/SkMatrix.cpp
+++ b/src/core/SkMatrix.cpp
@@ -848,7 +848,8 @@ bool SkMatrix::asAffine(SkScalar affine[6]) const {
return true;
}
-bool SkMatrix::invert(SkMatrix* inv) const {
+bool SkMatrix::invertNonIdentity(SkMatrix* inv) const {
+ SkASSERT(!this->isIdentity());
int isPersp = this->hasPerspective();
int shift;
SkDetScalar scale = sk_inv_determinant(fMat, isPersp, &shift);
diff --git a/src/core/SkOrderedReadBuffer.cpp b/src/core/SkOrderedReadBuffer.cpp
index 0a7bd904a3..29c036e49d 100644
--- a/src/core/SkOrderedReadBuffer.cpp
+++ b/src/core/SkOrderedReadBuffer.cpp
@@ -187,6 +187,7 @@ void SkOrderedReadBuffer::readBitmap(SkBitmap* bitmap) {
} else {
if (fBitmapStorage) {
const uint32_t index = fReader.readU32();
+ fReader.readU32(); // bitmap generation ID (see SkOrderedWriteBuffer::writeBitmap)
*bitmap = *fBitmapStorage->getBitmap(index);
fBitmapStorage->releaseRef(index);
} else {
diff --git a/src/core/SkOrderedWriteBuffer.cpp b/src/core/SkOrderedWriteBuffer.cpp
index bdd9516b25..7db0312ef4 100644
--- a/src/core/SkOrderedWriteBuffer.cpp
+++ b/src/core/SkOrderedWriteBuffer.cpp
@@ -7,6 +7,9 @@
*/
#include "SkOrderedWriteBuffer.h"
+#include "SkBitmap.h"
+#include "SkData.h"
+#include "SkPixelRef.h"
#include "SkPtrRecorder.h"
#include "SkStream.h"
#include "SkTypeface.h"
@@ -137,37 +140,72 @@ bool SkOrderedWriteBuffer::writeToStream(SkWStream* stream) {
}
void SkOrderedWriteBuffer::writeBitmap(const SkBitmap& bitmap) {
+ // Record information about the bitmap in one of three ways, in order of priority:
+ // 1. If there is an SkBitmapHeap, store it in the heap. The client can avoid serializing the
+ // bitmap entirely or serialize it later as desired.
+ // 2. Write an encoded version of the bitmap. Afterwards the width and height are written, so
+ // a reader without a decoder can draw a dummy bitmap of the right size.
+ // A. If the bitmap has an encoded representation, write it to the stream.
+ // B. If there is a function for encoding bitmaps, use it.
+ // 3. Call SkBitmap::flatten.
+ // For an encoded bitmap, write the size first. Otherwise store a 0 so the reader knows not to
+ // decode.
+ if (fBitmapHeap != NULL) {
+ SkASSERT(NULL == fBitmapEncoder);
+ // Bitmap was not encoded. Record a zero, implying that the reader need not decode.
+ this->writeUInt(0);
+ int32_t slot = fBitmapHeap->insert(bitmap);
+ fWriter.write32(slot);
+ // crbug.com/155875
+ // The generation ID is not required information. We write it to prevent collisions
+ // in SkFlatDictionary. It is possible to get a collision when a previously
+ // unflattened (i.e. stale) instance of a similar flattenable is in the dictionary
+ // and the instance currently being written is re-using the same slot from the
+ // bitmap heap.
+ fWriter.write32(bitmap.getGenerationID());
+ return;
+ }
bool encoded = false;
- if (fBitmapEncoder != NULL) {
- SkDynamicMemoryWStream pngStream;
- if (fBitmapEncoder(&pngStream, bitmap)) {
+ // Before attempting to encode the SkBitmap, check to see if there is already an encoded
+ // version.
+ SkPixelRef* ref = bitmap.pixelRef();
+ if (ref != NULL) {
+ SkAutoDataUnref data(ref->refEncodedData());
+ if (data.get() != NULL) {
+ // Write the length to indicate that the bitmap was encoded successfully, followed
+ // by the actual data. This must match the case where fBitmapEncoder is used so the
+ // reader need not know the difference.
+ this->writeUInt(data->size());
+ fWriter.writePad(data->data(), data->size());
encoded = true;
- if (encoded) {
- uint32_t offset = fWriter.bytesWritten();
- // Write the length to indicate that the bitmap was encoded successfully.
- size_t length = pngStream.getOffset();
- this->writeUInt(length);
- // Now write the stream.
- if (pngStream.read(fWriter.reservePad(length), 0, length)) {
- // Write the width and height in case the reader does not have a decoder.
- this->writeInt(bitmap.width());
- this->writeInt(bitmap.height());
- } else {
- // Writing the stream failed, so go back to original state to store another way.
- fWriter.rewindToOffset(offset);
- encoded = false;
- }
+ }
+ }
+ if (fBitmapEncoder != NULL && !encoded) {
+ SkASSERT(NULL == fBitmapHeap);
+ SkDynamicMemoryWStream stream;
+ if (fBitmapEncoder(&stream, bitmap)) {
+ uint32_t offset = fWriter.bytesWritten();
+ // Write the length to indicate that the bitmap was encoded successfully, followed
+ // by the actual data. This must match the case where the original data is used so the
+ // reader need not know the difference.
+ size_t length = stream.getOffset();
+ this->writeUInt(length);
+ if (stream.read(fWriter.reservePad(length), 0, length)) {
+ encoded = true;
+ } else {
+ // Writing the stream failed, so go back to original state to store another way.
+ fWriter.rewindToOffset(offset);
}
}
}
- if (!encoded) {
+ if (encoded) {
+ // Write the width and height in case the reader does not have a decoder.
+ this->writeInt(bitmap.width());
+ this->writeInt(bitmap.height());
+ } else {
// Bitmap was not encoded. Record a zero, implying that the reader need not decode.
this->writeUInt(0);
- if (fBitmapHeap) {
- fWriter.write32(fBitmapHeap->insert(bitmap));
- } else {
- bitmap.flatten(*this);
- }
+ bitmap.flatten(*this);
}
}
@@ -202,6 +240,23 @@ SkRefCntSet* SkOrderedWriteBuffer::setTypefaceRecorder(SkRefCntSet* rec) {
return rec;
}
+void SkOrderedWriteBuffer::setBitmapHeap(SkBitmapHeap* bitmapHeap) {
+ SkRefCnt_SafeAssign(fBitmapHeap, bitmapHeap);
+ if (bitmapHeap != NULL) {
+ SkASSERT(NULL == fBitmapEncoder);
+ fBitmapEncoder = NULL;
+ }
+}
+
+void SkOrderedWriteBuffer::setBitmapEncoder(SkSerializationHelpers::EncodeBitmap bitmapEncoder) {
+ fBitmapEncoder = bitmapEncoder;
+ if (bitmapEncoder != NULL) {
+ SkASSERT(NULL == fBitmapHeap);
+ SkSafeUnref(fBitmapHeap);
+ fBitmapHeap = NULL;
+ }
+}
+
void SkOrderedWriteBuffer::writeFlattenable(SkFlattenable* flattenable) {
/*
* If we have a factoryset, then the first 32bits tell us...
diff --git a/src/core/SkOrderedWriteBuffer.h b/src/core/SkOrderedWriteBuffer.h
index 681efedbdd..cd37f4721b 100644
--- a/src/core/SkOrderedWriteBuffer.h
+++ b/src/core/SkOrderedWriteBuffer.h
@@ -12,12 +12,12 @@
#include "SkFlattenableBuffers.h"
#include "SkRefCnt.h"
-#include "SkBitmap.h"
#include "SkBitmapHeap.h"
#include "SkPath.h"
#include "SkSerializationHelpers.h"
#include "SkWriter32.h"
+class SkBitmap;
class SkFlattenable;
class SkFactorySet;
class SkNamedFactorySet;
@@ -75,19 +75,23 @@ public:
SkRefCntSet* getTypefaceRecorder() const { return fTFSet; }
SkRefCntSet* setTypefaceRecorder(SkRefCntSet*);
- void setBitmapHeap(SkBitmapHeap* bitmapHeap) {
- SkRefCnt_SafeAssign(fBitmapHeap, bitmapHeap);
- }
+ /**
+ * Set an SkBitmapHeap to store bitmaps rather than flattening.
+ *
+ * Incompatible with an EncodeBitmap function. If an EncodeBitmap function is set, setting an
+ * SkBitmapHeap will set the function to NULL in release mode and crash in debug.
+ */
+ void setBitmapHeap(SkBitmapHeap*);
/**
* Provide a function to encode an SkBitmap to an SkStream. writeBitmap will attempt to use
- * bitmapEncoder to store the SkBitmap. Takes priority over the SkBitmapHeap. If the reader does
- * not provide a function to decode, it will not be able to restore SkBitmaps, but will still be
- * able to read the rest of the stream.
+ * bitmapEncoder to store the SkBitmap. If the reader does not provide a function to decode, it
+ * will not be able to restore SkBitmaps, but will still be able to read the rest of the stream.
+ *
+ * Incompatible with the SkBitmapHeap. If an encoder is set fBitmapHeap will be set to NULL in
+ * release and crash in debug.
*/
- void setBitmapEncoder(SkSerializationHelpers::EncodeBitmap bitmapEncoder) {
- fBitmapEncoder = bitmapEncoder;
- }
+ void setBitmapEncoder(SkSerializationHelpers::EncodeBitmap);
private:
SkFactorySet* fFactorySet;
diff --git a/src/core/SkPaint.cpp b/src/core/SkPaint.cpp
index a42cc88902..0d22a8e05e 100644
--- a/src/core/SkPaint.cpp
+++ b/src/core/SkPaint.cpp
@@ -1700,12 +1700,12 @@ static SkScalar gDeviceGamma = SK_ScalarMin;
* The caller must hold the gMaskGammaCacheMutex and continue to hold it until
* the returned SkMaskGamma pointer is refed or forgotten.
*/
-static SkMaskGamma* cachedMaskGamma(SkScalar contrast, SkScalar paintGamma, SkScalar deviceGamma) {
+static const SkMaskGamma& cachedMaskGamma(SkScalar contrast, SkScalar paintGamma, SkScalar deviceGamma) {
if (0 == contrast && SK_Scalar1 == paintGamma && SK_Scalar1 == deviceGamma) {
if (NULL == gLinearMaskGamma) {
gLinearMaskGamma = SkNEW(SkMaskGamma);
}
- return gLinearMaskGamma;
+ return *gLinearMaskGamma;
}
if (gContrast != contrast || gPaintGamma != paintGamma || gDeviceGamma != deviceGamma) {
SkSafeUnref(gMaskGamma);
@@ -1714,7 +1714,7 @@ static SkMaskGamma* cachedMaskGamma(SkScalar contrast, SkScalar paintGamma, SkSc
gPaintGamma = paintGamma;
gDeviceGamma = deviceGamma;
}
- return gMaskGamma;
+ return *gMaskGamma;
}
/*static*/ void SkPaint::Term() {
@@ -1743,7 +1743,7 @@ void SkScalerContext::PostMakeRec(const SkPaint& paint, SkScalerContext::Rec* re
case SkMask::kLCD32_Format: {
// filter down the luminance color to a finite number of bits
SkColor color = rec->getLuminanceColor();
- rec->setLuminanceColor(SkMaskGamma::cannonicalColor(color));
+ rec->setLuminanceColor(SkMaskGamma::CannonicalColor(color));
break;
}
case SkMask::kA8_Format: {
@@ -1760,7 +1760,7 @@ void SkScalerContext::PostMakeRec(const SkPaint& paint, SkScalerContext::Rec* re
// reduce to our finite number of bits
color = SkColorSetRGB(lum, lum, lum);
- rec->setLuminanceColor(SkMaskGamma::cannonicalColor(color));
+ rec->setLuminanceColor(SkMaskGamma::CannonicalColor(color));
break;
}
case SkMask::kBW_Format:
@@ -1815,6 +1815,11 @@ void SkPaint::descriptorProc(const SkMatrix* deviceMatrix,
descSize += mfBuffer.size();
entryCount += 1;
rec.fMaskFormat = SkMask::kA8_Format; // force antialiasing with maskfilters
+ /* Pre-blend is not currently applied to filtered text.
+ The primary filter is blur, for which contrast makes no sense,
+ and for which the destination guess error is more visible.
+ Also, all existing users of blur have calibrated for linear. */
+ rec.ignorePreBlend();
}
if (ra) {
raBuffer.writeFlattenable(ra);
@@ -1906,10 +1911,10 @@ SkGlyphCache* SkPaint::detachCache(const SkMatrix* deviceMatrix) const {
//static
SkMaskGamma::PreBlend SkScalerContext::GetMaskPreBlend(const SkScalerContext::Rec& rec) {
SkAutoMutexAcquire ama(gMaskGammaCacheMutex);
- SkMaskGamma* maskGamma = cachedMaskGamma(rec.getContrast(),
- rec.getPaintGamma(),
- rec.getDeviceGamma());
- return maskGamma->preBlend(rec.getLuminanceColor());
+ const SkMaskGamma& maskGamma = cachedMaskGamma(rec.getContrast(),
+ rec.getPaintGamma(),
+ rec.getDeviceGamma());
+ return maskGamma.preBlend(rec.getLuminanceColor());
}
///////////////////////////////////////////////////////////////////////////////
diff --git a/src/core/SkPath.cpp b/src/core/SkPath.cpp
index c0cf661afc..64a3ff69fa 100644
--- a/src/core/SkPath.cpp
+++ b/src/core/SkPath.cpp
@@ -11,9 +11,9 @@
#include "SkBuffer.h"
#include "SkMath.h"
#include "SkPathRef.h"
+#include "SkRRect.h"
#include "SkThread.h"
-
////////////////////////////////////////////////////////////////////////////
#if SK_DEBUG_PATH_REF
@@ -110,6 +110,21 @@ private:
bool fSaved;
};
+class SkAutoDisableDirectionCheck {
+public:
+ SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
+ fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
+ }
+
+ ~SkAutoDisableDirectionCheck() {
+ fPath->fDirection = fSaved;
+ }
+
+private:
+ SkPath* fPath;
+ SkPath::Direction fSaved;
+};
+
/* This guy's constructor/destructor bracket a path editing operation. It is
used when we know the bounds of the amount we are going to add to the path
(usually a new contour, but not required).
@@ -157,7 +172,9 @@ private:
// returns true if we should proceed
void init(SkPath* path) {
fPath = path;
- fDirty = SkToBool(path->fBoundsIsDirty);
+ // Mark the path's bounds as dirty if (1) they are, or (2) the path
+ // is non-finite, and therefore its bounds are not meaningful
+ fDirty = SkToBool(path->fBoundsIsDirty) || !path->fIsFinite;
fDegenerate = is_degenerate(*path);
fEmpty = path->isEmpty();
// Cannot use fRect for our bounds unless we know it is sorted
@@ -204,6 +221,7 @@ SkPath::SkPath()
, fFillType(kWinding_FillType)
, fBoundsIsDirty(true) {
fConvexity = kUnknown_Convexity;
+ fDirection = kUnknown_Direction;
fSegmentMask = 0;
fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
fIsOval = false;
@@ -226,6 +244,7 @@ SkPath::SkPath(const SkPath& src)
fFillType = src.fFillType;
fBoundsIsDirty = src.fBoundsIsDirty;
fConvexity = src.fConvexity;
+ fDirection = src.fDirection;
fIsFinite = src.fIsFinite;
fSegmentMask = src.fSegmentMask;
fLastMoveToIndex = src.fLastMoveToIndex;
@@ -251,6 +270,7 @@ SkPath& SkPath::operator=(const SkPath& src) {
fFillType = src.fFillType;
fBoundsIsDirty = src.fBoundsIsDirty;
fConvexity = src.fConvexity;
+ fDirection = src.fDirection;
fIsFinite = src.fIsFinite;
fSegmentMask = src.fSegmentMask;
fLastMoveToIndex = src.fLastMoveToIndex;
@@ -283,6 +303,7 @@ void SkPath::swap(SkPath& other) {
SkTSwap<uint8_t>(fFillType, other.fFillType);
SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
SkTSwap<uint8_t>(fConvexity, other.fConvexity);
+ SkTSwap<uint8_t>(fDirection, other.fDirection);
SkTSwap<uint8_t>(fSegmentMask, other.fSegmentMask);
SkTSwap<int>(fLastMoveToIndex, other.fLastMoveToIndex);
SkTSwap<SkBool8>(fIsOval, other.fIsOval);
@@ -291,6 +312,86 @@ void SkPath::swap(SkPath& other) {
}
}
+static inline bool check_edge_against_rect(const SkPoint& p0,
+ const SkPoint& p1,
+ const SkRect& rect,
+ SkPath::Direction dir) {
+ const SkPoint* edgeBegin;
+ SkVector v;
+ if (SkPath::kCW_Direction == dir) {
+ v = p1 - p0;
+ edgeBegin = &p0;
+ } else {
+ v = p0 - p1;
+ edgeBegin = &p1;
+ }
+ if (v.fX || v.fY) {
+ // check the cross product of v with the vec from edgeBegin to each rect corner
+ SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
+ SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
+ SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
+ SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
+ if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
+ // This only handles non-degenerate convex paths currently.
+ if (kConvex_Convexity != this->getConvexity()) {
+ return false;
+ }
+
+ Direction direction;
+ if (!this->cheapComputeDirection(&direction)) {
+ return false;
+ }
+
+ SkPoint firstPt;
+ SkPoint prevPt;
+ RawIter iter(*this);
+ SkPath::Verb verb;
+ SkPoint pts[4];
+ SkDEBUGCODE(int moveCnt = 0;)
+
+ while ((verb = iter.next(pts)) != kDone_Verb) {
+ int nextPt = -1;
+ switch (verb) {
+ case kMove_Verb:
+ SkASSERT(!moveCnt);
+ SkDEBUGCODE(++moveCnt);
+ firstPt = prevPt = pts[0];
+ break;
+ case kLine_Verb:
+ nextPt = 1;
+ SkASSERT(moveCnt);
+ break;
+ case kQuad_Verb:
+ SkASSERT(moveCnt);
+ nextPt = 2;
+ break;
+ case kCubic_Verb:
+ SkASSERT(moveCnt);
+ nextPt = 3;
+ break;
+ case kClose_Verb:
+ break;
+ default:
+ SkDEBUGFAIL("unknown verb");
+ }
+ if (-1 != nextPt) {
+ if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
+ return false;
+ }
+ prevPt = pts[nextPt];
+ }
+ }
+
+ return check_edge_against_rect(prevPt, firstPt, rect, direction);
+}
+
#ifdef SK_BUILD_FOR_ANDROID
uint32_t SkPath::getGenerationID() const {
return fGenerationID;
@@ -312,6 +413,7 @@ void SkPath::reset() {
GEN_ID_INC;
fBoundsIsDirty = true;
fConvexity = kUnknown_Convexity;
+ fDirection = kUnknown_Direction;
fSegmentMask = 0;
fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
fIsOval = false;
@@ -358,9 +460,9 @@ bool SkPath::isLine(SkPoint line[2]) const {
The direction is computed such that:
0: vertical up
- 1: horizontal right
+ 1: horizontal left
2: vertical down
- 3: horizontal left
+ 3: horizontal right
A rectangle cycles up/right/down/left or up/left/down/right.
@@ -387,11 +489,12 @@ FIXME: Allow colinear quads and cubics to be treated like lines.
FIXME: If the API passes fill-only, return true if the filled stroke
is a rectangle, though the caller failed to close the path.
*/
-bool SkPath::isRect(SkRect* rect) const {
- SkDEBUGCODE(this->validate();)
-
+bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
+ bool* isClosed, Direction* direction) const {
int corners = 0;
SkPoint first, last;
+ const SkPoint* pts = *ptsPtr;
+ const SkPoint* savePts = NULL;
first.set(0, 0);
last.set(0, 0);
int firstDirection = 0;
@@ -399,13 +502,12 @@ bool SkPath::isRect(SkRect* rect) const {
int nextDirection = 0;
bool closedOrMoved = false;
bool autoClose = false;
- const SkPoint* pts = fPathRef->points();
int verbCnt = fPathRef->countVerbs();
- int currVerb = 0;
- while (currVerb < verbCnt) {
- switch (fPathRef->atVerb(currVerb++)) {
+ while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
+ switch (fPathRef->atVerb(*currVerb)) {
case kClose_Verb:
- pts = fPathRef->points();
+ savePts = pts;
+ pts = *ptsPtr;
autoClose = true;
case kLine_Verb: {
SkScalar left = last.fX;
@@ -432,6 +534,9 @@ bool SkPath::isRect(SkRect* rect) const {
if (closedOrMoved) {
return false; // closed followed by a line
}
+ if (autoClose && nextDirection == firstDirection) {
+ break; // colinear with first
+ }
closedOrMoved = autoClose;
if (lastDirection != nextDirection) {
if (++corners > 4) {
@@ -460,16 +565,72 @@ bool SkPath::isRect(SkRect* rect) const {
closedOrMoved = true;
break;
}
+ *currVerb += 1;
lastDirection = nextDirection;
}
// Success if 4 corners and first point equals last
- bool result = 4 == corners && first == last;
+ bool result = 4 == corners && (first == last || autoClose);
+ if (savePts) {
+ *ptsPtr = savePts;
+ }
+ if (result && isClosed) {
+ *isClosed = autoClose;
+ }
+ if (result && direction) {
+ *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
+ }
+ return result;
+}
+
+bool SkPath::isRect(SkRect* rect) const {
+ SkDEBUGCODE(this->validate();)
+ int currVerb = 0;
+ const SkPoint* pts = fPathRef->points();
+ bool result = isRectContour(false, &currVerb, &pts, NULL, NULL);
if (result && rect) {
*rect = getBounds();
}
return result;
}
+bool SkPath::isRect(bool* isClosed, Direction* direction) const {
+ SkDEBUGCODE(this->validate();)
+ int currVerb = 0;
+ const SkPoint* pts = fPathRef->points();
+ return isRectContour(false, &currVerb, &pts, isClosed, direction);
+}
+
+bool SkPath::isNestedRects(SkRect rects[2]) const {
+ SkDEBUGCODE(this->validate();)
+ int currVerb = 0;
+ const SkPoint* pts = fPathRef->points();
+ const SkPoint* first = pts;
+ if (!isRectContour(true, &currVerb, &pts, NULL, NULL)) {
+ return false;
+ }
+ const SkPoint* last = pts;
+ SkRect testRects[2];
+ if (isRectContour(false, &currVerb, &pts, NULL, NULL)) {
+ testRects[0].set(first, last - first);
+ testRects[1].set(last, pts - last);
+ if (testRects[0].contains(testRects[1])) {
+ if (rects) {
+ rects[0] = testRects[0];
+ rects[1] = testRects[1];
+ }
+ return true;
+ }
+ if (testRects[1].contains(testRects[0])) {
+ if (rects) {
+ rects[0] = testRects[1];
+ rects[1] = testRects[0];
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
int SkPath::countPoints() const {
return fPathRef->countPoints();
}
@@ -565,12 +726,13 @@ void SkPath::setConvexity(Convexity c) {
do { \
fBoundsIsDirty = true; \
fConvexity = kUnknown_Convexity; \
+ fDirection = kUnknown_Direction; \
fIsOval = false; \
} while (0)
-#define DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE \
- do { \
- fBoundsIsDirty = true; \
+#define DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE \
+ do { \
+ fBoundsIsDirty = true; \
} while (0)
void SkPath::incReserve(U16CPU inc) {
@@ -590,7 +752,7 @@ void SkPath::moveTo(SkScalar x, SkScalar y) {
ed.growForVerb(kMove_Verb)->set(x, y);
GEN_ID_INC;
- DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE;
+ DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE;
}
void SkPath::rMoveTo(SkScalar x, SkScalar y) {
@@ -711,12 +873,20 @@ void SkPath::close() {
///////////////////////////////////////////////////////////////////////////////
+static void assert_known_direction(int dir) {
+ SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
+}
+
void SkPath::addRect(const SkRect& rect, Direction dir) {
this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
}
void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
SkScalar bottom, Direction dir) {
+ assert_known_direction(dir);
+ fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
+ SkAutoDisableDirectionCheck addc(this);
+
SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
this->incReserve(5);
@@ -774,6 +944,8 @@ void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
Direction dir) {
+ assert_known_direction(dir);
+
SkScalar w = rect.width();
SkScalar halfW = SkScalarHalf(w);
SkScalar h = rect.height();
@@ -791,7 +963,10 @@ void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
return;
}
+ fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
+
SkAutoPathBoundsUpdate apbu(this, rect);
+ SkAutoDisableDirectionCheck(this);
if (skip_hori) {
rx = halfW;
@@ -861,8 +1036,12 @@ void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
static void add_corner_arc(SkPath* path, const SkRect& rect,
SkScalar rx, SkScalar ry, int startAngle,
SkPath::Direction dir, bool forceMoveTo) {
- rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
- ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
+ // These two asserts are not sufficient, since really we want to know
+ // that the pair of radii (e.g. left and right, or top and bottom) sum
+ // to <= dimension, but we don't have that data here, so we just have
+ // these conservative asserts.
+ SkASSERT(0 <= rx && rx <= rect.width());
+ SkASSERT(0 <= ry && ry <= rect.height());
SkRect r;
r.set(-rx, -ry, rx, ry);
@@ -889,27 +1068,45 @@ static void add_corner_arc(SkPath* path, const SkRect& rect,
path->arcTo(r, start, sweep, forceMoveTo);
}
-void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
+void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
Direction dir) {
- // abort before we invoke SkAutoPathBoundsUpdate()
- if (rect.isEmpty()) {
+ SkRRect rrect;
+ rrect.setRectRadii(rect, (const SkVector*) radii);
+ this->addRRect(rrect, dir);
+}
+
+void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
+ assert_known_direction(dir);
+
+ if (rrect.isEmpty()) {
return;
}
- SkAutoPathBoundsUpdate apbu(this, rect);
+ const SkRect& bounds = rrect.getBounds();
- if (kCW_Direction == dir) {
- add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
- add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
- add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
- add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
+ if (rrect.isRect()) {
+ this->addRect(bounds, dir);
+ } else if (rrect.isOval()) {
+ this->addOval(bounds, dir);
+ } else if (rrect.isSimple()) {
+ const SkVector& rad = rrect.getSimpleRadii();
+ this->addRoundRect(bounds, rad.x(), rad.y(), dir);
} else {
- add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
- add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
- add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
- add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
+ SkAutoPathBoundsUpdate apbu(this, bounds);
+
+ if (kCW_Direction == dir) {
+ add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
+ add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
+ add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
+ add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
+ } else {
+ add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
+ add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
+ add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
+ add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
+ }
+ this->close();
}
- this->close();
}
bool SkPath::hasOnlyMoveTos() const {
@@ -927,6 +1124,8 @@ bool SkPath::hasOnlyMoveTos() const {
}
void SkPath::addOval(const SkRect& oval, Direction dir) {
+ assert_known_direction(dir);
+
/* If addOval() is called after previous moveTo(),
this path is still marked as an oval. This is used to
fit into WebKit's calling sequences.
@@ -934,8 +1133,14 @@ void SkPath::addOval(const SkRect& oval, Direction dir) {
moveTo() would mark the path non empty.
*/
fIsOval = hasOnlyMoveTos();
+ if (fIsOval) {
+ fDirection = dir;
+ } else {
+ fDirection = kUnknown_Direction;
+ }
SkAutoDisableOvalCheck adoc(this);
+ SkAutoDisableDirectionCheck addc(this);
SkAutoPathBoundsUpdate apbu(this, oval);
@@ -1031,6 +1236,15 @@ static int build_arc_points(const SkRect& oval, SkScalar startAngle,
// bounding box (and break the circle special case).
pts[0].set(oval.fRight, oval.centerY());
return 1;
+ } else if (0 == oval.width() && 0 == oval.height()) {
+ // Chrome will sometimes create 0 radius round rects. Having degenerate
+ // quad segments in the path prevents the path from being recognized as
+ // a rect.
+ // TODO: optimizing the case where only one of width or height is zero
+ // should also be considered. This case, however, doesn't seem to be
+ // as common as the single point case.
+ pts[0].set(oval.fRight, oval.fTop);
+ return 1;
}
SkVector start, stop;
@@ -1447,6 +1661,7 @@ void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
dst->swap(tmp);
SkPathRef::Editor ed(&dst->fPathRef);
matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
+ dst->fDirection = kUnknown_Direction;
} else {
/*
* If we're not in perspective, we can transform all of the points at
@@ -1488,6 +1703,22 @@ void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
if (!matrix.isIdentity()) {
GEN_ID_PTR_INC(dst);
}
+
+ if (kUnknown_Direction == fDirection) {
+ dst->fDirection = kUnknown_Direction;
+ } else {
+ SkScalar det2x2 =
+ SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
+ SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
+ if (det2x2 < 0) {
+ dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
+ } else if (det2x2 > 0) {
+ dst->fDirection = fDirection;
+ } else {
+ dst->fDirection = kUnknown_Direction;
+ }
+ }
+
// It's an oval only if it stays a rect.
dst->fIsOval = fIsOval && matrix.rectStaysRect();
@@ -1852,7 +2083,8 @@ uint32_t SkPath::writeToMemory(void* storage) const {
((fIsOval & 1) << kIsOval_SerializationShift) |
(fConvexity << kConvexity_SerializationShift) |
(fFillType << kFillType_SerializationShift) |
- (fSegmentMask << kSegmentMask_SerializationShift);
+ (fSegmentMask << kSegmentMask_SerializationShift) |
+ (fDirection << kDirection_SerializationShift);
buffer.write32(packed);
@@ -1876,7 +2108,8 @@ uint32_t SkPath::readFromMemory(const void* storage) {
fIsOval = (packed >> kIsOval_SerializationShift) & 1;
fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
- fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0xFF;
+ fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0x7;
+ fDirection = (packed >> kDirection_SerializationShift) & 0x3;
#if NEW_PICTURE_FORMAT
fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer));
@@ -1947,6 +2180,7 @@ void SkPath::validate() const {
SkASSERT(this != NULL);
SkASSERT((fFillType & ~3) == 0);
+#ifdef SK_DEBUG_PATH
if (!fBoundsIsDirty) {
SkRect bounds;
@@ -1995,8 +2229,9 @@ void SkPath::validate() const {
}
}
SkASSERT(mask == fSegmentMask);
+#endif // SK_DEBUG_PATH
}
-#endif
+#endif // SK_DEBUG
///////////////////////////////////////////////////////////////////////////////
@@ -2009,7 +2244,10 @@ static int CrossProductSign(const SkVector& a, const SkVector& b) {
// only valid for a single contour
struct Convexicator {
- Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
+ Convexicator()
+ : fPtCount(0)
+ , fConvexity(SkPath::kConvex_Convexity)
+ , fDirection(SkPath::kUnknown_Direction) {
fSign = 0;
// warnings
fCurrPt.set(0, 0);
@@ -2023,6 +2261,9 @@ struct Convexicator {
SkPath::Convexity getConvexity() const { return fConvexity; }
+ /** The direction returned is only valid if the path is determined convex */
+ SkPath::Direction getDirection() const { return fDirection; }
+
void addPt(const SkPoint& pt) {
if (SkPath::kConcave_Convexity == fConvexity) {
return;
@@ -2070,9 +2311,15 @@ private:
int sign = CrossProductSign(fVec0, fVec1);
if (0 == fSign) {
fSign = sign;
+ if (1 == sign) {
+ fDirection = SkPath::kCW_Direction;
+ } else if (-1 == sign) {
+ fDirection = SkPath::kCCW_Direction;
+ }
} else if (sign) {
if (fSign != sign) {
fConvexity = SkPath::kConcave_Convexity;
+ fDirection = SkPath::kUnknown_Direction;
}
}
}
@@ -2082,13 +2329,15 @@ private:
int fPtCount; // non-degenerate points
int fSign;
SkPath::Convexity fConvexity;
+ SkPath::Direction fDirection;
int fDx, fDy, fSx, fSy;
};
-SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
+SkPath::Convexity SkPath::internalGetConvexity() const {
+ SkASSERT(kUnknown_Convexity == fConvexity);
SkPoint pts[4];
SkPath::Verb verb;
- SkPath::Iter iter(path, true);
+ SkPath::Iter iter(*this, true);
int contourCount = 0;
int count;
@@ -2098,6 +2347,7 @@ SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
switch (verb) {
case kMove_Verb:
if (++contourCount > 1) {
+ fConvexity = kConcave_Convexity;
return kConcave_Convexity;
}
pts[1] = pts[0];
@@ -2112,6 +2362,7 @@ SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
break;
default:
SkDEBUGFAIL("bad verb");
+ fConvexity = kConcave_Convexity;
return kConcave_Convexity;
}
@@ -2120,10 +2371,15 @@ SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
}
// early exit
if (kConcave_Convexity == state.getConvexity()) {
+ fConvexity = kConcave_Convexity;
return kConcave_Convexity;
}
}
- return state.getConvexity();
+ fConvexity = state.getConvexity();
+ if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
+ fDirection = state.getDirection();
+ }
+ return static_cast<Convexity>(fConvexity);
}
///////////////////////////////////////////////////////////////////////////////
@@ -2274,11 +2530,10 @@ static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
return minIndex;
}
-static bool crossToDir(SkScalar cross, SkPath::Direction* dir) {
+static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
if (dir) {
*dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
}
- return true;
}
#if 0
@@ -2348,6 +2603,11 @@ bool SkPath::cheapComputeDirection(Direction* dir) const {
// dumpPath(*this);
// don't want to pay the cost for computing this if it
// is unknown, so we don't call isConvex()
+
+ if (kUnknown_Direction != fDirection) {
+ *dir = static_cast<Direction>(fDirection);
+ return true;
+ }
const Convexity conv = this->getConvexityOrUnknown();
ContourIter iter(*fPathRef.get());
@@ -2369,9 +2629,11 @@ bool SkPath::cheapComputeDirection(Direction* dir) const {
// precision. This is because the vectors computed between distant
// points may lose too much precision.
if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
+ fDirection = *dir;
return true;
}
if (convex_dir_test<double, toDouble>(n, pts, dir)) {
+ fDirection = *dir;
return true;
} else {
return false;
@@ -2415,9 +2677,10 @@ bool SkPath::cheapComputeDirection(Direction* dir) const {
int next = find_diff_pt(pts, index, n, 1);
SkASSERT(next != index);
cross = cross_prod(pts[prev], pts[index], pts[next]);
- // if we get a zero, but the pts aren't on top of each other, then
- // we can just look at the direction
- if (0 == cross) {
+ // if we get a zero and the points are horizontal, then we look at the spread in
+ // x-direction. We really should continue to walk away from the degeneracy until
+ // there is a divergence.
+ if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
// construct the subtract so we get the correct Direction below
cross = pts[index].fX - pts[next].fX;
}
@@ -2430,8 +2693,13 @@ bool SkPath::cheapComputeDirection(Direction* dir) const {
}
}
}
-
- return ymaxCross ? crossToDir(ymaxCross, dir) : false;
+ if (ymaxCross) {
+ crossToDir(ymaxCross, dir);
+ fDirection = *dir;
+ return true;
+ } else {
+ return false;
+ }
}
///////////////////////////////////////////////////////////////////////////////
diff --git a/src/core/SkPathEffect.cpp b/src/core/SkPathEffect.cpp
index d706e8dabe..ebbfd6dc51 100644
--- a/src/core/SkPathEffect.cpp
+++ b/src/core/SkPathEffect.cpp
@@ -6,116 +6,23 @@
* found in the LICENSE file.
*/
-
#include "SkPathEffect.h"
#include "SkPath.h"
#include "SkFlattenableBuffers.h"
-#include "SkPaintDefaults.h"
-
-// must be < 0, since ==0 means hairline, and >0 means normal stroke
-#define kStrokeRec_FillStyleWidth (-SK_Scalar1)
-
-SkStrokeRec::SkStrokeRec(InitStyle s) {
- fWidth = (kFill_InitStyle == s) ? kStrokeRec_FillStyleWidth : 0;
- fMiterLimit = SkPaintDefaults_MiterLimit;
- fCap = SkPaint::kDefault_Cap;
- fJoin = SkPaint::kDefault_Join;
- fStrokeAndFill = false;
-}
-
-SkStrokeRec::SkStrokeRec(const SkStrokeRec& src) {
- memcpy(this, &src, sizeof(src));
-}
-
-SkStrokeRec::SkStrokeRec(const SkPaint& paint) {
- switch (paint.getStyle()) {
- case SkPaint::kFill_Style:
- fWidth = kStrokeRec_FillStyleWidth;
- fStrokeAndFill = false;
- break;
- case SkPaint::kStroke_Style:
- fWidth = paint.getStrokeWidth();
- fStrokeAndFill = false;
- break;
- case SkPaint::kStrokeAndFill_Style:
- if (0 == paint.getStrokeWidth()) {
- // hairline+fill == fill
- fWidth = kStrokeRec_FillStyleWidth;
- fStrokeAndFill = false;
- } else {
- fWidth = paint.getStrokeWidth();
- fStrokeAndFill = true;
- }
- break;
- default:
- SkASSERT(!"unknown paint style");
- // fall back on just fill
- fWidth = kStrokeRec_FillStyleWidth;
- fStrokeAndFill = false;
- break;
- }
-
- // copy these from the paint, regardless of our "style"
- fMiterLimit = paint.getStrokeMiter();
- fCap = paint.getStrokeCap();
- fJoin = paint.getStrokeJoin();
-}
-
-SkStrokeRec::Style SkStrokeRec::getStyle() const {
- if (fWidth < 0) {
- return kFill_Style;
- } else if (0 == fWidth) {
- return kHairline_Style;
- } else {
- return fStrokeAndFill ? kStrokeAndFill_Style : kStroke_Style;
- }
-}
-
-void SkStrokeRec::setFillStyle() {
- fWidth = kStrokeRec_FillStyleWidth;
- fStrokeAndFill = false;
-}
-
-void SkStrokeRec::setHairlineStyle() {
- fWidth = 0;
- fStrokeAndFill = false;
-}
-
-void SkStrokeRec::setStrokeStyle(SkScalar width, bool strokeAndFill) {
- if (strokeAndFill && (0 == width)) {
- // hairline+fill == fill
- this->setFillStyle();
- } else {
- fWidth = width;
- fStrokeAndFill = strokeAndFill;
- }
-}
-
-#include "SkStroke.h"
-
-bool SkStrokeRec::applyToPath(SkPath* dst, const SkPath& src) const {
- if (fWidth <= 0) { // hairline or fill
- return false;
- }
-
- SkStroke stroker;
- stroker.setCap(fCap);
- stroker.setJoin(fJoin);
- stroker.setMiterLimit(fMiterLimit);
- stroker.setWidth(fWidth);
- stroker.setDoFill(fStrokeAndFill);
- stroker.strokePath(src, dst);
- return true;
-}
///////////////////////////////////////////////////////////////////////////////
SK_DEFINE_INST_COUNT(SkPathEffect)
-void SkPathEffect::computeFastBounds(SkRect* dst, const SkRect& src) {
+void SkPathEffect::computeFastBounds(SkRect* dst, const SkRect& src) const {
*dst = src;
}
+bool SkPathEffect::asPoints(PointData* results, const SkPath& src,
+ const SkStrokeRec&, const SkMatrix&) const {
+ return false;
+}
+
///////////////////////////////////////////////////////////////////////////////
SkPairPathEffect::SkPairPathEffect(SkPathEffect* pe0, SkPathEffect* pe1)
@@ -149,7 +56,7 @@ SkPairPathEffect::SkPairPathEffect(SkFlattenableReadBuffer& buffer) {
///////////////////////////////////////////////////////////////////////////////
bool SkComposePathEffect::filterPath(SkPath* dst, const SkPath& src,
- SkStrokeRec* rec) {
+ SkStrokeRec* rec) const {
// we may have failed to unflatten these, so we have to check
if (!fPE0 || !fPE1) {
return false;
@@ -167,7 +74,7 @@ bool SkComposePathEffect::filterPath(SkPath* dst, const SkPath& src,
///////////////////////////////////////////////////////////////////////////////
bool SkSumPathEffect::filterPath(SkPath* dst, const SkPath& src,
- SkStrokeRec* rec) {
+ SkStrokeRec* rec) const {
// use bit-or so that we always call both, even if the first one succeeds
return fPE0->filterPath(dst, src, rec) | fPE1->filterPath(dst, src, rec);
}
diff --git a/src/core/SkPicture.cpp b/src/core/SkPicture.cpp
index cc73bda5e4..b9273df467 100644
--- a/src/core/SkPicture.cpp
+++ b/src/core/SkPicture.cpp
@@ -157,6 +157,12 @@ void SkPicture::clone(SkPicture* pictures, int count) const {
clone->fHeight = fHeight;
clone->fRecord = NULL;
+ if (NULL != clone->fRecord) {
+ clone->fRecord->unref();
+ clone->fRecord = NULL;
+ }
+ SkDELETE(clone->fPlayback);
+
/* We want to copy the src's playback. However, if that hasn't been built
yet, we need to fake a call to endRecording() without actually calling
it (since it is destructive, and we don't want to change src).
@@ -190,11 +196,12 @@ SkCanvas* SkPicture::beginRecording(int width, int height,
bm.setConfig(SkBitmap::kNo_Config, width, height);
SkAutoTUnref<SkDevice> dev(SkNEW_ARGS(SkDevice, (bm)));
+ // Must be set before calling createBBoxHierarchy
+ fWidth = width;
+ fHeight = height;
+
if (recordingFlags & kOptimizeForClippedPlayback_RecordingFlag) {
- SkScalar aspectRatio = SkScalarDiv(SkIntToScalar(width),
- SkIntToScalar(height));
- SkRTree* tree = SkRTree::Create(kRTreeMinChildren, kRTreeMaxChildren,
- aspectRatio);
+ SkBBoxHierarchy* tree = this->createBBoxHierarchy();
SkASSERT(NULL != tree);
fRecord = SkNEW_ARGS(SkBBoxHierarchyRecord, (recordingFlags, tree, dev));
tree->unref();
@@ -203,12 +210,21 @@ SkCanvas* SkPicture::beginRecording(int width, int height,
}
fRecord->beginRecording();
- fWidth = width;
- fHeight = height;
-
return fRecord;
}
+SkBBoxHierarchy* SkPicture::createBBoxHierarchy() const {
+ // These values were empirically determined to produce reasonable
+ // performance in most cases.
+ static const int kRTreeMinChildren = 6;
+ static const int kRTreeMaxChildren = 11;
+
+ SkScalar aspectRatio = SkScalarDiv(SkIntToScalar(fWidth),
+ SkIntToScalar(fHeight));
+ return SkRTree::Create(kRTreeMinChildren, kRTreeMaxChildren,
+ aspectRatio);
+}
+
SkCanvas* SkPicture::getRecordingCanvas() const {
// will be null if we are not recording
return fRecord;
@@ -237,17 +253,6 @@ void SkPicture::draw(SkCanvas* surface) {
#include "SkStream.h"
-// V2 : adds SkPixelRef's generation ID.
-// V3 : PictInfo tag at beginning, and EOF tag at the end
-// V4 : move SkPictInfo to be the header
-// V5 : don't read/write FunctionPtr on cross-process (we can detect that)
-// V6 : added serialization of SkPath's bounds (and packed its flags tighter)
-// V7 : changed drawBitmapRect(IRect) to drawBitmapRectToRect(Rect)
-// V8 : Add an option for encoding bitmaps
-// V9 : Allow the reader and writer of an SKP disagree on whether to support
-// SK_SUPPORT_HINTING_SCALE_FACTOR
-#define PICTURE_VERSION 9
-
SkPicture::SkPicture(SkStream* stream, bool* success, SkSerializationHelpers::DecodeBitmap decoder) : SkRefCnt() {
if (success) {
*success = false;
diff --git a/src/core/SkPictureFlat.cpp b/src/core/SkPictureFlat.cpp
index 8f6e0895d7..e104a402bc 100644
--- a/src/core/SkPictureFlat.cpp
+++ b/src/core/SkPictureFlat.cpp
@@ -120,6 +120,7 @@ SkFlatData* SkFlatData::Create(SkFlatController* controller, const void* obj,
SkFlatData* result = (SkFlatData*) controller->allocThrow(allocSize);
result->fIndex = index;
+ result->fTopBot[0] = result->fTopBot[1] = 0; // initial to sentinel values
result->fFlatSize = size;
// put the serialized contents into the data section of the new allocation
diff --git a/src/core/SkPictureFlat.h b/src/core/SkPictureFlat.h
index 2c8aa2c18e..9594a594dd 100644
--- a/src/core/SkPictureFlat.h
+++ b/src/core/SkPictureFlat.h
@@ -29,6 +29,7 @@ enum DrawType {
CLIP_PATH,
CLIP_REGION,
CLIP_RECT,
+ CLIP_RRECT,
CONCAT,
DRAW_BITMAP,
DRAW_BITMAP_MATRIX,
@@ -36,6 +37,7 @@ enum DrawType {
DRAW_BITMAP_RECT_TO_RECT,
DRAW_CLEAR,
DRAW_DATA,
+ DRAW_OVAL,
DRAW_PAINT,
DRAW_PATH,
DRAW_PICTURE,
@@ -45,6 +47,7 @@ enum DrawType {
DRAW_POS_TEXT_H,
DRAW_POS_TEXT_H_TOP_BOTTOM, // fast variant of DRAW_POS_TEXT_H
DRAW_RECT,
+ DRAW_RRECT,
DRAW_SPRITE,
DRAW_TEXT,
DRAW_TEXT_ON_PATH,
@@ -329,9 +332,34 @@ public:
return !memcmp(a.dataToCompare(), b.dataToCompare(), N);
}
+ // returns true if fTopBot[] has been recorded
+ bool isTopBotValid() const {
+ return fTopBot[0] < fTopBot[1];
+ }
+
+ // Returns fTopBot array, so it can be passed to a routine to compute them.
+ // For efficiency, we assert that fTopBot have not been recorded yet.
+ SkScalar* writableTopBot() {
+ SkASSERT(!this->isTopBotValid());
+ return fTopBot;
+ }
+
+ // return the topbot[] after it has been recorded
+ const SkScalar* topBot() const {
+ SkASSERT(this->isTopBotValid());
+ return fTopBot;
+ }
+
private:
+ // This is *not* part of the key for search/sort
int fIndex;
+ // Cache of paint's FontMetrics fTop,fBottom
+ // initialied to [0,0] as a sentinel that they have not been recorded yet
+ //
+ // This is *not* part of the key for search/sort
+ SkScalar fTopBot[2];
+
// From here down is the data we look at in the search/sort. We always begin
// with the checksum and then length.
uint32_t fChecksum;
@@ -448,20 +476,6 @@ public:
}
/**
- * Given a pointer to an array of type T we allocate the array and fill it
- * with the unflattened dictionary contents. The return value is the size of
- * the allocated array.
- */
- int unflattenDictionary(T*& array) const {
- int elementCount = fData.count();
- if (elementCount > 0) {
- array = SkNEW_ARRAY(T, elementCount);
- this->unflattenIntoArray(array);
- }
- return elementCount;
- }
-
- /**
* Unflatten the objects and return them in SkTRefArray, or return NULL
* if there no objects (instead of an empty array).
*/
@@ -635,6 +649,10 @@ class SkPaintDictionary : public SkFlatDictionary<SkPaint> {
fFlattenProc = &SkFlattenObjectProc<SkPaint>;
fUnflattenProc = &SkUnflattenObjectProc<SkPaint>;
}
+
+ SkFlatData* writableFlatData(int index) {
+ return const_cast<SkFlatData*>((*this)[index]);
+ }
};
class SkRegionDictionary : public SkFlatDictionary<SkRegion> {
diff --git a/src/core/SkPicturePlayback.cpp b/src/core/SkPicturePlayback.cpp
index 5d4098a872..e7f5ce8f85 100644
--- a/src/core/SkPicturePlayback.cpp
+++ b/src/core/SkPicturePlayback.cpp
@@ -146,6 +146,24 @@ SkPicturePlayback::SkPicturePlayback(const SkPictureRecord& record, bool deepCop
#endif
}
+static bool needs_deep_copy(const SkPaint& paint) {
+ /*
+ * These fields are known to be immutable, and so can be shallow-copied
+ *
+ * getTypeface();
+ * getAnnotation();
+ */
+
+ return paint.getPathEffect() ||
+ paint.getShader() ||
+ paint.getXfermode() ||
+ paint.getMaskFilter() ||
+ paint.getColorFilter() ||
+ paint.getRasterizer() ||
+ paint.getLooper() ||
+ paint.getImageFilter();
+}
+
SkPicturePlayback::SkPicturePlayback(const SkPicturePlayback& src, SkPictCopyInfo* deepCopyInfo) {
this->init();
@@ -176,11 +194,30 @@ SkPicturePlayback::SkPicturePlayback(const SkPicturePlayback& src, SkPictCopyInf
*/
deepCopyInfo->paintData.setCount(src.fPaints->count());
+ /* Use an SkBitmapHeap to avoid flattening bitmaps in shaders. If there already is one,
+ * use it. If this SkPicturePlayback was created from a stream, fBitmapHeap will be
+ * NULL, so create a new one.
+ */
+ if (fBitmapHeap.get() == NULL) {
+ // FIXME: Put this on the stack inside SkPicture::clone. Further, is it possible to
+ // do the rest of this initialization in SkPicture::clone as well?
+ SkBitmapHeap* heap = SkNEW(SkBitmapHeap);
+ deepCopyInfo->controller.setBitmapStorage(heap);
+ heap->unref();
+ } else {
+ deepCopyInfo->controller.setBitmapStorage(fBitmapHeap);
+ }
+
SkDEBUGCODE(int heapSize = SafeCount(fBitmapHeap.get());)
for (int i = 0; i < src.fPaints->count(); i++) {
- deepCopyInfo->paintData[i] = SkFlatData::Create(&deepCopyInfo->controller,
- &src.fPaints->at(i), 0,
- &SkFlattenObjectProc<SkPaint>);
+ if (needs_deep_copy(src.fPaints->at(i))) {
+ deepCopyInfo->paintData[i] = SkFlatData::Create(&deepCopyInfo->controller,
+ &src.fPaints->at(i), 0,
+ &SkFlattenObjectProc<SkPaint>);
+ } else {
+ // this is our sentinel, which we use in the unflatten loop
+ deepCopyInfo->paintData[i] = NULL;
+ }
}
SkASSERT(SafeCount(fBitmapHeap.get()) == heapSize);
@@ -191,11 +228,17 @@ SkPicturePlayback::SkPicturePlayback(const SkPicturePlayback& src, SkPictCopyInf
fPaints = SkTRefArray<SkPaint>::Create(src.fPaints->count());
SkASSERT(deepCopyInfo->paintData.count() == src.fPaints->count());
+ SkBitmapHeap* bmHeap = deepCopyInfo->controller.getBitmapHeap();
+ SkTypefacePlayback* tfPlayback = deepCopyInfo->controller.getTypefacePlayback();
for (int i = 0; i < src.fPaints->count(); i++) {
- deepCopyInfo->paintData[i]->unflatten(&fPaints->writableAt(i),
- &SkUnflattenObjectProc<SkPaint>,
- deepCopyInfo->controller.getBitmapHeap(),
- deepCopyInfo->controller.getTypefacePlayback());
+ if (deepCopyInfo->paintData[i]) {
+ deepCopyInfo->paintData[i]->unflatten(&fPaints->writableAt(i),
+ &SkUnflattenObjectProc<SkPaint>,
+ bmHeap, tfPlayback);
+ } else {
+ // needs_deep_copy was false, so just need to assign
+ fPaints->writableAt(i) = src.fPaints->at(i);
+ }
}
} else {
@@ -505,7 +548,9 @@ bool SkPicturePlayback::parseBufferTag(SkOrderedReadBuffer& buffer,
case PICT_BITMAP_BUFFER_TAG: {
fBitmaps = SkTRefArray<SkBitmap>::Create(size);
for (size_t i = 0; i < size; ++i) {
- buffer.readBitmap(&fBitmaps->writableAt(i));
+ SkBitmap* bm = &fBitmaps->writableAt(i);
+ buffer.readBitmap(bm);
+ bm->setImmutable();
}
} break;
case PICT_MATRIX_BUFFER_TAG:
@@ -574,19 +619,32 @@ struct SkipClipRec {
};
#endif
+#ifdef SK_PICTURE_PROFILING_STUBS
+size_t SkPicturePlayback::preDraw(size_t offset, int type) {
+ return 0;
+}
+
+void SkPicturePlayback::postDraw(size_t offset) {
+}
+#endif
+
void SkPicturePlayback::draw(SkCanvas& canvas) {
#ifdef ENABLE_TIME_DRAW
SkAutoTime at("SkPicture::draw", 50);
#endif
#ifdef SPEW_CLIP_SKIPPING
- SkipClipRec skipRect, skipRegion, skipPath;
+ SkipClipRec skipRect, skipRRect, skipRegion, skipPath;
#endif
#ifdef SK_BUILD_FOR_ANDROID
SkAutoMutexAcquire autoMutex(fDrawMutex);
#endif
+ // kDrawComplete will be the signal that we have reached the end of
+ // the command stream
+ static const uint32_t kDrawComplete = SK_MaxU32;
+
SkReader32 reader(fOpData->bytes(), fOpData->size());
TextContainer text;
SkTDArray<void*> results;
@@ -611,18 +669,32 @@ void SkPicturePlayback::draw(SkCanvas& canvas) {
fStateTree->getIterator(results, &canvas);
if (it.isValid()) {
- uint32_t off = it.draw();
- if (off == SK_MaxU32) {
+ uint32_t skipTo = it.draw();
+ if (kDrawComplete == skipTo) {
return;
}
- reader.setOffset(off);
+ reader.setOffset(skipTo);
}
// Record this, so we can concat w/ it if we encounter a setMatrix()
SkMatrix initialMatrix = canvas.getTotalMatrix();
while (!reader.eof()) {
- switch (reader.readInt()) {
+#ifdef SK_PICTURE_PROFILING_STUBS
+ size_t curOffset = reader.offset();
+#endif
+ int type = reader.readInt();
+#ifdef SK_PICTURE_PROFILING_STUBS
+ size_t skipTo = this->preDraw(curOffset, type);
+ if (0 != skipTo) {
+ if (kDrawComplete == skipTo) {
+ break;
+ }
+ reader.setOffset(skipTo);
+ continue;
+ }
+#endif
+ switch (type) {
case CLIP_PATH: {
const SkPath& path = getPath(reader);
uint32_t packed = reader.readInt();
@@ -659,7 +731,7 @@ void SkPicturePlayback::draw(SkCanvas& canvas) {
bool doAA = ClipParams_unpackDoAA(packed);
size_t offsetToRestore = reader.readInt();
SkASSERT(!offsetToRestore || \
- offsetToRestore >= reader.offset());
+ offsetToRestore >= reader.offset());
if (!canvas.clipRect(rect, op, doAA) && offsetToRestore) {
#ifdef SPEW_CLIP_SKIPPING
skipRect.recordSkip(offsetToRestore - reader.offset());
@@ -667,6 +739,22 @@ void SkPicturePlayback::draw(SkCanvas& canvas) {
reader.setOffset(offsetToRestore);
}
} break;
+ case CLIP_RRECT: {
+ SkRRect rrect;
+ reader.readRRect(&rrect);
+ uint32_t packed = reader.readInt();
+ SkRegion::Op op = ClipParams_unpackRegionOp(packed);
+ bool doAA = ClipParams_unpackDoAA(packed);
+ size_t offsetToRestore = reader.readInt();
+ SkASSERT(!offsetToRestore || \
+ offsetToRestore >= reader.offset());
+ if (!canvas.clipRRect(rrect, op, doAA) && offsetToRestore) {
+#ifdef SPEW_CLIP_SKIPPING
+ skipRRect.recordSkip(offsetToRestore - reader.offset());
+#endif
+ reader.setOffset(offsetToRestore);
+ }
+ } break;
case CONCAT:
canvas.concat(*getMatrix(reader));
break;
@@ -704,6 +792,10 @@ void SkPicturePlayback::draw(SkCanvas& canvas) {
canvas.drawData(reader.skip(length), length);
// skip handles padding the read out to a multiple of 4
} break;
+ case DRAW_OVAL: {
+ const SkPaint& paint = *getPaint(reader);
+ canvas.drawOval(reader.skipT<SkRect>(), paint);
+ } break;
case DRAW_PAINT:
canvas.drawPaint(*getPaint(reader));
break;
@@ -765,6 +857,11 @@ void SkPicturePlayback::draw(SkCanvas& canvas) {
const SkPaint& paint = *getPaint(reader);
canvas.drawRect(reader.skipT<SkRect>(), paint);
} break;
+ case DRAW_RRECT: {
+ const SkPaint& paint = *getPaint(reader);
+ SkRRect rrect;
+ canvas.drawRRect(*reader.readRRect(&rrect), paint);
+ } break;
case DRAW_SPRITE: {
const SkPaint* paint = getPaint(reader);
const SkBitmap& bitmap = getBitmap(reader);
@@ -865,21 +962,25 @@ void SkPicturePlayback::draw(SkCanvas& canvas) {
SkASSERT(0);
}
+#ifdef SK_PICTURE_PROFILING_STUBS
+ this->postDraw(curOffset);
+#endif
+
if (it.isValid()) {
- uint32_t off = it.draw();
- if (off == SK_MaxU32) {
+ uint32_t skipTo = it.draw();
+ if (kDrawComplete == skipTo) {
break;
}
- reader.setOffset(off);
+ reader.setOffset(skipTo);
}
}
#ifdef SPEW_CLIP_SKIPPING
{
- size_t size = skipRect.fSize + skipPath.fSize + skipRegion.fSize;
- SkDebugf("--- Clip skips %d%% rect:%d path:%d rgn:%d\n",
- size * 100 / reader.offset(), skipRect.fCount, skipPath.fCount,
- skipRegion.fCount);
+ size_t size = skipRect.fSize + skipRRect.fSize + skipPath.fSize + skipRegion.fSize;
+ SkDebugf("--- Clip skips %d%% rect:%d rrect:%d path:%d rgn:%d\n",
+ size * 100 / reader.offset(), skipRect.fCount, skipRRect.fCount,
+ skipPath.fCount, skipRegion.fCount);
}
#endif
// this->dumpSize();
diff --git a/src/core/SkPicturePlayback.h b/src/core/SkPicturePlayback.h
index 24bce4e7eb..b04ba07281 100644
--- a/src/core/SkPicturePlayback.h
+++ b/src/core/SkPicturePlayback.h
@@ -19,6 +19,7 @@
#include "SkPath.h"
#include "SkPathHeap.h"
#include "SkRegion.h"
+#include "SkRRect.h"
#include "SkPictureFlat.h"
#include "SkSerializationHelpers.h"
@@ -77,6 +78,12 @@ public:
// drawing and return from draw() after the "current" op code is done
void abort();
+protected:
+#ifdef SK_PICTURE_PROFILING_STUBS
+ virtual size_t preDraw(size_t offset, int type);
+ virtual void postDraw(size_t offset);
+#endif
+
private:
class TextContainer {
public:
@@ -87,7 +94,11 @@ private:
};
const SkBitmap& getBitmap(SkReader32& reader) {
- int index = reader.readInt();
+ const int index = reader.readInt();
+ if (SkBitmapHeap::INVALID_SLOT == index) {
+ SkDebugf("An invalid bitmap was recorded!\n");
+ return fBadBitmap;
+ }
return (*fBitmaps)[index];
}
@@ -184,6 +195,10 @@ private: // these help us with reading/writing
void flattenToBuffer(SkOrderedWriteBuffer&) const;
private:
+ // Only used by getBitmap() if the passed in index is SkBitmapHeap::INVALID_SLOT. This empty
+ // bitmap allows playback to draw nothing and move on.
+ SkBitmap fBadBitmap;
+
SkAutoTUnref<SkBitmapHeap> fBitmapHeap;
SkAutoTUnref<SkPathHeap> fPathHeap;
diff --git a/src/core/SkPictureRecord.cpp b/src/core/SkPictureRecord.cpp
index edc67a064e..4010387660 100644
--- a/src/core/SkPictureRecord.cpp
+++ b/src/core/SkPictureRecord.cpp
@@ -8,6 +8,7 @@
#include "SkPictureRecord.h"
#include "SkTSearch.h"
#include "SkPixelRef.h"
+#include "SkRRect.h"
#include "SkBBoxHierarchy.h"
#include "SkPictureStateTree.h"
@@ -111,6 +112,7 @@ static inline uint32_t getSkipableSize(unsigned drawType) {
4, // CLIP_PATH,
4, // CLIP_REGION,
7, // CLIP_RECT,
+ 15, // CLIP_RRECT,
2, // CONCAT,
0, // DRAW_BITMAP,
0, // DRAW_BITMAP_MATRIX,
@@ -118,6 +120,7 @@ static inline uint32_t getSkipableSize(unsigned drawType) {
0, // DRAW_BITMAP_RECT,
0, // DRAW_CLEAR,
0, // DRAW_DATA,
+ 0, // DRAW_OVAL,
0, // DRAW_PAINT,
0, // DRAW_PATH,
0, // DRAW_PICTURE,
@@ -127,6 +130,7 @@ static inline uint32_t getSkipableSize(unsigned drawType) {
0, // DRAW_POS_TEXT_H,
0, // DRAW_POS_TEXT_H_TOP_BOTTOM, // fast variant of DRAW_POS_TEXT_H
0, // DRAW_RECT,
+ 0, // DRAW_RRECT,
0, // DRAW_SPRITE,
0, // DRAW_TEXT,
0, // DRAW_TEXT_ON_PATH,
@@ -160,13 +164,6 @@ static inline uint32_t getSkipableSize(unsigned drawType) {
* the restore() call. If we still need the restore(), return false.
*/
static bool collapseSaveClipRestore(SkWriter32* writer, int32_t offset) {
- // Some unexplained crashes in Chrome may be caused by this. Disabling
- // for now to see if it helps.
- // crbug.com/147406
-#ifdef SK_DISABLE_PICTURE_PEEPHOLE_OPTIMIZATION
- return false;
-#endif
-
#ifdef TRACK_COLLAPSE_STATS
gCollapseCalls += 1;
#endif
@@ -364,7 +361,32 @@ bool SkPictureRecord::clipRect(const SkRect& rect, SkRegion::Op op, bool doAA) {
return this->INHERITED::clipRect(rect, op, doAA);
}
+bool SkPictureRecord::clipRRect(const SkRRect& rrect, SkRegion::Op op, bool doAA) {
+ if (rrect.isRect()) {
+ return this->SkPictureRecord::clipRect(rrect.getBounds(), op, doAA);
+ }
+
+ addDraw(CLIP_RRECT);
+ addRRect(rrect);
+ addInt(ClipParams_pack(op, doAA));
+ recordRestoreOffsetPlaceholder(op);
+
+ validate();
+
+ if (fRecordFlags & SkPicture::kUsePathBoundsForClip_RecordingFlag) {
+ return this->INHERITED::clipRect(rrect.getBounds(), op, doAA);
+ } else {
+ return this->INHERITED::clipRRect(rrect, op, doAA);
+ }
+}
+
bool SkPictureRecord::clipPath(const SkPath& path, SkRegion::Op op, bool doAA) {
+
+ SkRect r;
+ if (!path.isInverseFillType() && path.isRect(&r)) {
+ return this->clipRect(r, op, doAA);
+ }
+
addDraw(CLIP_PATH);
addPath(path);
addInt(ClipParams_pack(op, doAA));
@@ -411,6 +433,13 @@ void SkPictureRecord::drawPoints(PointMode mode, size_t count, const SkPoint pts
validate();
}
+void SkPictureRecord::drawOval(const SkRect& oval, const SkPaint& paint) {
+ addDraw(DRAW_OVAL);
+ addPaint(paint);
+ addRect(oval);
+ validate();
+}
+
void SkPictureRecord::drawRect(const SkRect& rect, const SkPaint& paint) {
addDraw(DRAW_RECT);
addPaint(paint);
@@ -418,6 +447,23 @@ void SkPictureRecord::drawRect(const SkRect& rect, const SkPaint& paint) {
validate();
}
+void SkPictureRecord::drawRRect(const SkRRect& rrect, const SkPaint& paint) {
+ if (rrect.isRect()) {
+ addDraw(DRAW_RECT);
+ addPaint(paint);
+ addRect(rrect.getBounds());
+ } else if (rrect.isOval()) {
+ addDraw(DRAW_OVAL);
+ addPaint(paint);
+ addRect(rrect.getBounds());
+ } else {
+ addDraw(DRAW_RRECT);
+ addPaint(paint);
+ addRRect(rrect);
+ }
+ validate();
+}
+
void SkPictureRecord::drawPath(const SkPath& path, const SkPaint& paint) {
addDraw(DRAW_PATH);
addPaint(paint);
@@ -474,19 +520,30 @@ void SkPictureRecord::drawSprite(const SkBitmap& bitmap, int left, int top,
validate();
}
-void SkPictureRecord::addFontMetricsTopBottom(const SkPaint& paint,
- SkScalar minY, SkScalar maxY) {
+// Return fontmetrics.fTop,fBottom in topbot[0,1], after they have been
+// tweaked by paint.computeFastBounds().
+//
+static void computeFontMetricsTopBottom(const SkPaint& paint, SkScalar topbot[2]) {
SkPaint::FontMetrics metrics;
paint.getFontMetrics(&metrics);
SkRect bounds;
// construct a rect so we can see any adjustments from the paint.
// we use 0,1 for left,right, just so the rect isn't empty
- bounds.set(0, metrics.fTop + minY,
- SK_Scalar1, metrics.fBottom + maxY);
+ bounds.set(0, metrics.fTop, SK_Scalar1, metrics.fBottom);
(void)paint.computeFastBounds(bounds, &bounds);
- // now record the top and bottom
- addScalar(bounds.fTop);
- addScalar(bounds.fBottom);
+ topbot[0] = bounds.fTop;
+ topbot[1] = bounds.fBottom;
+}
+
+void SkPictureRecord::addFontMetricsTopBottom(const SkPaint& paint, int index,
+ SkScalar minY, SkScalar maxY) {
+ SkFlatData* flat = fPaints.writableFlatData(index);
+ if (!flat->isTopBotValid()) {
+ computeFontMetricsTopBottom(paint, flat->writableTopBot());
+ SkASSERT(flat->isTopBotValid());
+ }
+ addScalar(flat->topBot()[0] + minY);
+ addScalar(flat->topBot()[1] + maxY);
}
void SkPictureRecord::drawText(const void* text, size_t byteLength, SkScalar x,
@@ -494,12 +551,12 @@ void SkPictureRecord::drawText(const void* text, size_t byteLength, SkScalar x,
bool fast = !paint.isVerticalText() && paint.canComputeFastBounds();
addDraw(fast ? DRAW_TEXT_TOP_BOTTOM : DRAW_TEXT);
- addPaint(paint);
+ int paintIndex = addPaint(paint);
addText(text, byteLength);
addScalar(x);
addScalar(y);
if (fast) {
- addFontMetricsTopBottom(paint, y, y);
+ addFontMetricsTopBottom(paint, paintIndex - 1, y, y);
}
validate();
}
@@ -540,7 +597,7 @@ void SkPictureRecord::drawPosText(const void* text, size_t byteLength,
} else {
addDraw(DRAW_POS_TEXT);
}
- addPaint(paint);
+ int paintIndex = addPaint(paint);
addText(text, byteLength);
addInt(points);
@@ -549,7 +606,7 @@ void SkPictureRecord::drawPosText(const void* text, size_t byteLength,
#endif
if (canUseDrawH) {
if (fast) {
- addFontMetricsTopBottom(paint, pos[0].fY, pos[0].fY);
+ addFontMetricsTopBottom(paint, paintIndex - 1, pos[0].fY, pos[0].fY);
}
addScalar(pos[0].fY);
SkScalar* xptr = (SkScalar*)fWriter.reserve(points * sizeof(SkScalar));
@@ -559,7 +616,7 @@ void SkPictureRecord::drawPosText(const void* text, size_t byteLength,
else {
fWriter.writeMul4(pos, points * sizeof(SkPoint));
if (fastBounds) {
- addFontMetricsTopBottom(paint, minY, maxY);
+ addFontMetricsTopBottom(paint, paintIndex - 1, minY, maxY);
}
}
#ifdef SK_DEBUG_SIZE
@@ -579,7 +636,7 @@ void SkPictureRecord::drawPosTextH(const void* text, size_t byteLength,
bool fast = !paint.isVerticalText() && paint.canComputeFastBounds();
addDraw(fast ? DRAW_POS_TEXT_H_TOP_BOTTOM : DRAW_POS_TEXT_H);
- addPaint(paint);
+ int paintIndex = this->addPaint(paint);
addText(text, byteLength);
addInt(points);
@@ -587,7 +644,7 @@ void SkPictureRecord::drawPosTextH(const void* text, size_t byteLength,
size_t start = fWriter.size();
#endif
if (fast) {
- addFontMetricsTopBottom(paint, constY, constY);
+ addFontMetricsTopBottom(paint, paintIndex - 1, constY, constY);
}
addScalar(constY);
fWriter.writeMul4(xpos, points * sizeof(SkScalar));
@@ -658,7 +715,12 @@ void SkPictureRecord::drawData(const void* data, size_t length) {
///////////////////////////////////////////////////////////////////////////////
void SkPictureRecord::addBitmap(const SkBitmap& bitmap) {
- addInt(fBitmapHeap->insert(bitmap));
+ const int index = fBitmapHeap->insert(bitmap);
+ // In debug builds, a bad return value from insert() will crash, allowing for debugging. In
+ // release builds, the invalid value will be recorded so that the reader will know that there
+ // was a problem.
+ SkASSERT(index != SkBitmapHeap::INVALID_SLOT);
+ addInt(index);
}
void SkPictureRecord::addMatrix(const SkMatrix& matrix) {
@@ -669,12 +731,10 @@ void SkPictureRecord::addMatrixPtr(const SkMatrix* matrix) {
this->addInt(matrix ? fMatrices.find(*matrix) : 0);
}
-void SkPictureRecord::addPaint(const SkPaint& paint) {
- addPaintPtr(&paint);
-}
-
-void SkPictureRecord::addPaintPtr(const SkPaint* paint) {
- this->addInt(paint ? fPaints.find(*paint) : 0);
+int SkPictureRecord::addPaintPtr(const SkPaint* paint) {
+ int index = paint ? fPaints.find(*paint) : 0;
+ this->addInt(index);
+ return index;
}
void SkPictureRecord::addPath(const SkPath& path) {
@@ -741,6 +801,10 @@ void SkPictureRecord::addIRectPtr(const SkIRect* rect) {
}
}
+void SkPictureRecord::addRRect(const SkRRect& rrect) {
+ fWriter.writeRRect(rrect);
+}
+
void SkPictureRecord::addRegion(const SkRegion& region) {
addInt(fRegions.find(region));
}
diff --git a/src/core/SkPictureRecord.h b/src/core/SkPictureRecord.h
index c36c24ab3d..abaa22dd9d 100644
--- a/src/core/SkPictureRecord.h
+++ b/src/core/SkPictureRecord.h
@@ -36,13 +36,16 @@ public:
virtual bool concat(const SkMatrix& matrix) SK_OVERRIDE;
virtual void setMatrix(const SkMatrix& matrix) SK_OVERRIDE;
virtual bool clipRect(const SkRect&, SkRegion::Op, bool) SK_OVERRIDE;
+ virtual bool clipRRect(const SkRRect&, SkRegion::Op, bool) SK_OVERRIDE;
virtual bool clipPath(const SkPath&, SkRegion::Op, bool) SK_OVERRIDE;
virtual bool clipRegion(const SkRegion& region, SkRegion::Op op) SK_OVERRIDE;
virtual void clear(SkColor) SK_OVERRIDE;
virtual void drawPaint(const SkPaint& paint) SK_OVERRIDE;
virtual void drawPoints(PointMode, size_t count, const SkPoint pts[],
const SkPaint&) SK_OVERRIDE;
- virtual void drawRect(const SkRect& rect, const SkPaint&) SK_OVERRIDE;
+ virtual void drawOval(const SkRect&, const SkPaint&) SK_OVERRIDE;
+ virtual void drawRect(const SkRect&, const SkPaint&) SK_OVERRIDE;
+ virtual void drawRRect(const SkRRect&, const SkPaint&) SK_OVERRIDE;
virtual void drawPath(const SkPath& path, const SkPaint&) SK_OVERRIDE;
virtual void drawBitmap(const SkBitmap&, SkScalar left, SkScalar top,
const SkPaint*) SK_OVERRIDE;
@@ -72,7 +75,8 @@ public:
virtual void drawData(const void*, size_t) SK_OVERRIDE;
virtual bool isDrawingToLayer() const SK_OVERRIDE;
- void addFontMetricsTopBottom(const SkPaint& paint, SkScalar minY, SkScalar maxY);
+ void addFontMetricsTopBottom(const SkPaint& paint, int paintIndex,
+ SkScalar minY, SkScalar maxY);
const SkTDArray<SkPicture* >& getPictureRefs() const {
return fPictureRefs;
@@ -118,8 +122,8 @@ private:
void addBitmap(const SkBitmap& bitmap);
void addMatrix(const SkMatrix& matrix);
void addMatrixPtr(const SkMatrix* matrix);
- void addPaint(const SkPaint& paint);
- void addPaintPtr(const SkPaint* paint);
+ int addPaint(const SkPaint& paint) { return this->addPaintPtr(&paint); }
+ int addPaintPtr(const SkPaint* paint);
void addPath(const SkPath& path);
void addPicture(SkPicture& picture);
void addPoint(const SkPoint& point);
@@ -128,6 +132,7 @@ private:
void addRectPtr(const SkRect* rect);
void addIRect(const SkIRect& rect);
void addIRectPtr(const SkIRect* rect);
+ void addRRect(const SkRRect&);
void addRegion(const SkRegion& region);
void addText(const void* text, size_t byteLength);
@@ -174,8 +179,10 @@ protected:
SkBBoxHierarchy* fBoundingHierarchy;
SkPictureStateTree* fStateTree;
-private:
+ // Allocated in the constructor and managed by this class.
SkBitmapHeap* fBitmapHeap;
+
+private:
SkChunkFlatController fFlattenableHeap;
SkMatrixDictionary fMatrices;
diff --git a/src/core/SkPixelRef.cpp b/src/core/SkPixelRef.cpp
index 69bbad7502..4cf47e3a2e 100644
--- a/src/core/SkPixelRef.cpp
+++ b/src/core/SkPixelRef.cpp
@@ -81,12 +81,14 @@ SkPixelRef::SkPixelRef(SkFlattenableReadBuffer& buffer, SkBaseMutex* mutex)
}
void SkPixelRef::setPreLocked(void* pixels, SkColorTable* ctable) {
+#ifndef SK_IGNORE_PIXELREF_SETPRELOCKED
// only call me in your constructor, otherwise fLockCount tracking can get
// out of sync.
fPixels = pixels;
fColorTable = ctable;
fLockCount = SKPIXELREF_PRELOCKED_LOCKCOUNT;
fPreLocked = true;
+#endif
}
void SkPixelRef::flatten(SkFlattenableWriteBuffer& buffer) const {
@@ -168,6 +170,10 @@ bool SkPixelRef::onReadPixels(SkBitmap* dst, const SkIRect* subset) {
return false;
}
+SkData* SkPixelRef::onRefEncodedData() {
+ return NULL;
+}
+
///////////////////////////////////////////////////////////////////////////////
#ifdef SK_BUILD_FOR_ANDROID
diff --git a/src/core/SkRRect.cpp b/src/core/SkRRect.cpp
new file mode 100644
index 0000000000..0d137ec953
--- /dev/null
+++ b/src/core/SkRRect.cpp
@@ -0,0 +1,328 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkRRect.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkRRect::setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad) {
+ if (rect.isEmpty()) {
+ this->setEmpty();
+ return;
+ }
+
+ if (xRad <= 0 || yRad <= 0) {
+ // all corners are square in this case
+ this->setRect(rect);
+ return;
+ }
+
+ if (rect.width() < xRad+xRad || rect.height() < yRad+yRad) {
+ SkScalar scale = SkMinScalar(SkScalarDiv(rect.width(), xRad + xRad),
+ SkScalarDiv(rect.height(), yRad + yRad));
+ SkASSERT(scale < SK_Scalar1);
+ xRad = SkScalarMul(xRad, scale);
+ yRad = SkScalarMul(yRad, scale);
+ }
+
+ fRect = rect;
+ for (int i = 0; i < 4; ++i) {
+ fRadii[i].set(xRad, yRad);
+ }
+ fType = kSimple_Type;
+ if (xRad >= SkScalarHalf(fRect.width()) && yRad >= SkScalarHalf(fRect.height())) {
+ fType = kOval_Type;
+ // TODO: try asserting they are already W/2 & H/2 already
+ xRad = SkScalarHalf(fRect.width());
+ yRad = SkScalarHalf(fRect.height());
+ }
+
+ SkDEBUGCODE(this->validate();)
+}
+
+void SkRRect::setRectRadii(const SkRect& rect, const SkVector radii[4]) {
+ if (rect.isEmpty()) {
+ this->setEmpty();
+ return;
+ }
+
+ fRect = rect;
+ memcpy(fRadii, radii, sizeof(fRadii));
+
+ bool allCornersSquare = true;
+
+ // Clamp negative radii to zero
+ for (int i = 0; i < 4; ++i) {
+ if (fRadii[i].fX <= 0 || fRadii[i].fY <= 0) {
+ // In this case we are being a little fast & loose. Since one of
+ // the radii is 0 the corner is square. However, the other radii
+ // could still be non-zero and play in the global scale factor
+ // computation.
+ fRadii[i].fX = 0;
+ fRadii[i].fY = 0;
+ } else {
+ allCornersSquare = false;
+ }
+ }
+
+ if (allCornersSquare) {
+ this->setRect(rect);
+ return;
+ }
+
+ // Proportionally scale down all radii to fit. Find the minimum ratio
+ // of a side and the radii on that side (for all four sides) and use
+ // that to scale down _all_ the radii. This algorithm is from the
+ // W3 spec (http://www.w3.org/TR/css3-background/) section 5.5 - Overlapping
+ // Curves:
+ // "Let f = min(Li/Si), where i is one of { top, right, bottom, left },
+ // Si is the sum of the two corresponding radii of the corners on side i,
+ // and Ltop = Lbottom = the width of the box,
+ // and Lleft = Lright = the height of the box.
+ // If f < 1, then all corner radii are reduced by multiplying them by f."
+ SkScalar scale = SK_Scalar1;
+
+ if (fRadii[0].fX + fRadii[1].fX > rect.width()) {
+ scale = SkMinScalar(scale,
+ SkScalarDiv(rect.width(), fRadii[0].fX + fRadii[1].fX));
+ }
+ if (fRadii[1].fY + fRadii[2].fY > rect.height()) {
+ scale = SkMinScalar(scale,
+ SkScalarDiv(rect.height(), fRadii[1].fY + fRadii[2].fY));
+ }
+ if (fRadii[2].fX + fRadii[3].fX > rect.width()) {
+ scale = SkMinScalar(scale,
+ SkScalarDiv(rect.width(), fRadii[2].fX + fRadii[3].fX));
+ }
+ if (fRadii[3].fY + fRadii[0].fY > rect.height()) {
+ scale = SkMinScalar(scale,
+ SkScalarDiv(rect.height(), fRadii[3].fY + fRadii[0].fY));
+ }
+
+ if (scale < SK_Scalar1) {
+ for (int i = 0; i < 4; ++i) {
+ fRadii[i].fX = SkScalarMul(fRadii[i].fX, scale);
+ fRadii[i].fY = SkScalarMul(fRadii[i].fY, scale);
+ }
+ }
+
+ // At this point we're either oval, simple, or complex (not empty or rect)
+ // but we lazily resolve the type to avoid the work if the information
+ // isn't required.
+ fType = (SkRRect::Type) kUnknown_Type;
+
+ SkDEBUGCODE(this->validate();)
+}
+
+bool SkRRect::contains(SkScalar x, SkScalar y) const {
+ SkDEBUGCODE(this->validate();)
+
+ if (kEmpty_Type == this->type()) {
+ return false;
+ }
+
+ if (!fRect.contains(x, y)) {
+ return false;
+ }
+
+ if (kRect_Type == this->type()) {
+ // the 'fRect' test above was sufficient
+ return true;
+ }
+
+ // We know the point is inside the RR's bounds. The only way it can
+ // be out is if it outside one of the corners
+ SkPoint canonicalPt; // (x,y) translated to one of the quadrants
+ int index;
+
+ if (kOval_Type == this->type()) {
+ canonicalPt.set(x - fRect.centerX(), y - fRect.centerY());
+ index = kUpperLeft_Corner; // any corner will do in this case
+ } else {
+ if (x < fRect.fLeft + fRadii[kUpperLeft_Corner].fX &&
+ y < fRect.fTop + fRadii[kUpperLeft_Corner].fY) {
+ // UL corner
+ index = kUpperLeft_Corner;
+ canonicalPt.set(x - (fRect.fLeft + fRadii[kUpperLeft_Corner].fX),
+ y - (fRect.fTop + fRadii[kUpperLeft_Corner].fY));
+ SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY < 0);
+ } else if (x < fRect.fLeft + fRadii[kLowerLeft_Corner].fX &&
+ y > fRect.fBottom - fRadii[kLowerLeft_Corner].fY) {
+ // LL corner
+ index = kLowerLeft_Corner;
+ canonicalPt.set(x - (fRect.fLeft + fRadii[kLowerLeft_Corner].fX),
+ y - (fRect.fBottom - fRadii[kLowerLeft_Corner].fY));
+ SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY > 0);
+ } else if (x > fRect.fRight - fRadii[kUpperRight_Corner].fX &&
+ y < fRect.fTop + fRadii[kUpperRight_Corner].fY) {
+ // UR corner
+ index = kUpperRight_Corner;
+ canonicalPt.set(x - (fRect.fRight - fRadii[kUpperRight_Corner].fX),
+ y - (fRect.fTop + fRadii[kUpperRight_Corner].fY));
+ SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY < 0);
+ } else if (x > fRect.fRight - fRadii[kLowerRight_Corner].fX &&
+ y > fRect.fBottom - fRadii[kLowerRight_Corner].fY) {
+ // LR corner
+ index = kLowerRight_Corner;
+ canonicalPt.set(x - (fRect.fRight - fRadii[kLowerRight_Corner].fX),
+ y - (fRect.fBottom - fRadii[kLowerRight_Corner].fY));
+ SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY > 0);
+ } else {
+ // not in any of the corners
+ return true;
+ }
+ }
+
+ // A point is in an ellipse (in standard position) if:
+ // x^2 y^2
+ // ----- + ----- <= 1
+ // a^2 b^2
+ SkScalar dist = SkScalarDiv(SkScalarSquare(canonicalPt.fX), SkScalarSquare(fRadii[index].fX)) +
+ SkScalarDiv(SkScalarSquare(canonicalPt.fY), SkScalarSquare(fRadii[index].fY));
+ return dist <= SK_Scalar1;
+}
+
+// There is a simplified version of this method in setRectXY
+void SkRRect::computeType() const {
+ SkDEBUGCODE(this->validate();)
+
+ if (fRect.isEmpty()) {
+ fType = kEmpty_Type;
+ return;
+ }
+
+ bool allRadiiEqual = true; // are all x radii equal and all y radii?
+ bool allCornersSquare = 0 == fRadii[0].fX || 0 == fRadii[0].fY;
+
+ for (int i = 1; i < 4; ++i) {
+ if (0 != fRadii[i].fX && 0 != fRadii[i].fY) {
+ // if either radius is zero the corner is square so both have to
+ // be non-zero to have a rounded corner
+ allCornersSquare = false;
+ }
+ if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) {
+ allRadiiEqual = false;
+ }
+ }
+
+ if (allCornersSquare) {
+ fType = kRect_Type;
+ return;
+ }
+
+ if (allRadiiEqual) {
+ if (fRadii[0].fX >= SkScalarHalf(fRect.width()) &&
+ fRadii[0].fY >= SkScalarHalf(fRect.height())) {
+ fType = kOval_Type;
+ } else {
+ fType = kSimple_Type;
+ }
+ return;
+ }
+
+ fType = kComplex_Type;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+#if 0
+void SkRRect::inset(SkScalar dx, SkScalar dy, SkRRect* dst) const {
+
+ SkRect r = fRect;
+ r.inset(dx, dy);
+ if (r.isEmpty()) {
+ dst->setEmpty();
+ return;
+ }
+
+ SkVector radii[4];
+ for (int i = 0; i < 4; ++i) {
+ radii[i].set(fRadii[i].fX - dx, fRadii[i].fY - dy);
+ }
+ dst->setRectRadii(r, radii);
+}
+#endif
+///////////////////////////////////////////////////////////////////////////////
+
+uint32_t SkRRect::writeToMemory(void* buffer) const {
+ SkASSERT(kSizeInMemory == sizeof(SkRect) + sizeof(fRadii));
+
+ memcpy(buffer, &fRect, sizeof(SkRect));
+ memcpy((char*)buffer + sizeof(SkRect), fRadii, sizeof(fRadii));
+ return kSizeInMemory;
+}
+
+uint32_t SkRRect::readFromMemory(const void* buffer) {
+ SkScalar storage[12];
+ SkASSERT(sizeof(storage) == kSizeInMemory);
+
+ // we make a local copy, to ensure alignment before we cast
+ memcpy(storage, buffer, kSizeInMemory);
+
+ this->setRectRadii(*(const SkRect*)&storage[0],
+ (const SkVector*)&storage[4]);
+ return kSizeInMemory;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_DEBUG
+void SkRRect::validate() const {
+ bool allRadiiZero = (0 == fRadii[0].fX && 0 == fRadii[0].fY);
+ bool allCornersSquare = (0 == fRadii[0].fX || 0 == fRadii[0].fY);
+ bool allRadiiSame = true;
+
+ for (int i = 1; i < 4; ++i) {
+ if (0 != fRadii[i].fX || 0 != fRadii[i].fY) {
+ allRadiiZero = false;
+ }
+
+ if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) {
+ allRadiiSame = false;
+ }
+
+ if (0 != fRadii[i].fX && 0 != fRadii[i].fY) {
+ allCornersSquare = false;
+ }
+ }
+
+ switch (fType) {
+ case kEmpty_Type:
+ SkASSERT(fRect.isEmpty());
+ SkASSERT(allRadiiZero && allRadiiSame && allCornersSquare);
+
+ SkASSERT(0 == fRect.fLeft && 0 == fRect.fTop &&
+ 0 == fRect.fRight && 0 == fRect.fBottom);
+ break;
+ case kRect_Type:
+ SkASSERT(!fRect.isEmpty());
+ SkASSERT(allRadiiZero && allRadiiSame && allCornersSquare);
+ break;
+ case kOval_Type:
+ SkASSERT(!fRect.isEmpty());
+ SkASSERT(!allRadiiZero && allRadiiSame && !allCornersSquare);
+
+ for (int i = 0; i < 4; ++i) {
+ SkASSERT(SkScalarNearlyEqual(fRadii[i].fX, SkScalarHalf(fRect.width())));
+ SkASSERT(SkScalarNearlyEqual(fRadii[i].fY, SkScalarHalf(fRect.height())));
+ }
+ break;
+ case kSimple_Type:
+ SkASSERT(!fRect.isEmpty());
+ SkASSERT(!allRadiiZero && allRadiiSame && !allCornersSquare);
+ break;
+ case kComplex_Type:
+ SkASSERT(!fRect.isEmpty());
+ SkASSERT(!allRadiiZero && !allRadiiSame && !allCornersSquare);
+ break;
+ case kUnknown_Type:
+ // no limits on this
+ break;
+ }
+}
+#endif // SK_DEBUG
+
+///////////////////////////////////////////////////////////////////////////////
diff --git a/src/core/SkRasterizer.cpp b/src/core/SkRasterizer.cpp
index c6ddc086ca..23a749fd4b 100644
--- a/src/core/SkRasterizer.cpp
+++ b/src/core/SkRasterizer.cpp
@@ -16,7 +16,7 @@ SK_DEFINE_INST_COUNT(SkRasterizer)
bool SkRasterizer::rasterize(const SkPath& fillPath, const SkMatrix& matrix,
const SkIRect* clipBounds, SkMaskFilter* filter,
- SkMask* mask, SkMask::CreateMode mode) {
+ SkMask* mask, SkMask::CreateMode mode) const {
SkIRect storage;
if (clipBounds && filter && SkMask::kJustRenderImage_CreateMode != mode) {
@@ -41,7 +41,7 @@ bool SkRasterizer::rasterize(const SkPath& fillPath, const SkMatrix& matrix,
*/
bool SkRasterizer::onRasterize(const SkPath& fillPath, const SkMatrix& matrix,
const SkIRect* clipBounds,
- SkMask* mask, SkMask::CreateMode mode) {
+ SkMask* mask, SkMask::CreateMode mode) const {
SkPath devPath;
fillPath.transform(matrix, &devPath);
diff --git a/src/core/SkRegion.cpp b/src/core/SkRegion.cpp
index 0dcacd849e..776f3e8d33 100644
--- a/src/core/SkRegion.cpp
+++ b/src/core/SkRegion.cpp
@@ -313,8 +313,8 @@ bool SkRegion::contains(int32_t x, int32_t y) const {
if (this->isRect()) {
return true;
}
-
SkASSERT(this->isComplex());
+
const RunType* runs = fRunHead->findScanline(y);
// Skip the Bottom and IntervalCount
@@ -338,6 +338,10 @@ bool SkRegion::contains(int32_t x, int32_t y) const {
return false;
}
+static SkRegion::RunType scanline_bottom(const SkRegion::RunType runs[]) {
+ return runs[0];
+}
+
static const SkRegion::RunType* scanline_next(const SkRegion::RunType runs[]) {
// skip [B N [L R]... S]
return runs + 2 + runs[1] * 2 + 1;
@@ -367,16 +371,18 @@ bool SkRegion::contains(const SkIRect& r) const {
if (this->isRect()) {
return true;
}
-
SkASSERT(this->isComplex());
- const RunType* scanline = fRunHead->findScanline(r.fTop);
- do {
+ const RunType* scanline = fRunHead->findScanline(r.fTop);
+ for (;;) {
if (!scanline_contains(scanline, r.fLeft, r.fRight)) {
return false;
}
+ if (r.fBottom <= scanline_bottom(scanline)) {
+ break;
+ }
scanline = scanline_next(scanline);
- } while (r.fBottom >= scanline[0]);
+ }
return true;
}
@@ -450,14 +456,18 @@ bool SkRegion::intersects(const SkIRect& r) const {
if (this->isRect()) {
return true;
}
+ SkASSERT(this->isComplex());
const RunType* scanline = fRunHead->findScanline(sect.fTop);
- do {
+ for (;;) {
if (scanline_intersects(scanline, sect.fLeft, sect.fRight)) {
return true;
}
+ if (sect.fBottom <= scanline_bottom(scanline)) {
+ break;
+ }
scanline = scanline_next(scanline);
- } while (sect.fBottom >= scanline[0]);
+ }
return false;
}
diff --git a/src/core/SkScalerContext.cpp b/src/core/SkScalerContext.cpp
index dbd8c34856..afefef8db5 100644
--- a/src/core/SkScalerContext.cpp
+++ b/src/core/SkScalerContext.cpp
@@ -77,14 +77,21 @@ static SkFlattenable* load_flattenable(const SkDescriptor* desc, uint32_t tag) {
SkScalerContext::SkScalerContext(const SkDescriptor* desc)
: fRec(*static_cast<const Rec*>(desc->findEntry(kRec_SkDescriptorTag, NULL)))
+
, fBaseGlyphCount(0)
+
, fPathEffect(static_cast<SkPathEffect*>(load_flattenable(desc, kPathEffect_SkDescriptorTag)))
, fMaskFilter(static_cast<SkMaskFilter*>(load_flattenable(desc, kMaskFilter_SkDescriptorTag)))
, fRasterizer(static_cast<SkRasterizer*>(load_flattenable(desc, kRasterizer_SkDescriptorTag)))
- // initialize based on our settings. subclasses can also force this
+
+ // Initialize based on our settings. Subclasses can also force this.
, fGenerateImageFromPath(fRec.fFrameWidth > 0 || fPathEffect != NULL || fRasterizer != NULL)
+
, fNextContext(NULL)
- , fMaskPreBlend(SkScalerContext::GetMaskPreBlend(fRec))
+
+ , fPreBlend(fMaskFilter ? SkMaskGamma::PreBlend() : SkScalerContext::GetMaskPreBlend(fRec))
+ , fPreBlendForFilter(fMaskFilter ? SkScalerContext::GetMaskPreBlend(fRec)
+ : SkMaskGamma::PreBlend())
{
#ifdef DUMP_REC
desc->assertChecksum();
@@ -357,8 +364,22 @@ SK_ERROR:
glyph->fMaskFormat = fRec.fMaskFormat;
}
+
+static void applyLUTToA8Mask(const SkMask& mask, const uint8_t* lut) {
+ uint8_t* SK_RESTRICT dst = (uint8_t*)mask.fImage;
+ unsigned rowBytes = mask.fRowBytes;
+
+ for (int y = mask.fBounds.height() - 1; y >= 0; --y) {
+ for (int x = mask.fBounds.width() - 1; x >= 0; --x) {
+ dst[x] = lut[dst[x]];
+ }
+ dst += rowBytes;
+ }
+}
+
template<bool APPLY_PREBLEND>
-static void pack3xHToLCD16(const SkBitmap& src, const SkMask& dst, SkMaskGamma::PreBlend* maskPreBlend) {
+static void pack3xHToLCD16(const SkBitmap& src, const SkMask& dst,
+ const SkMaskGamma::PreBlend& maskPreBlend) {
SkASSERT(SkBitmap::kA8_Config == src.config());
SkASSERT(SkMask::kLCD16_Format == dst.fFormat);
@@ -367,21 +388,12 @@ static void pack3xHToLCD16(const SkBitmap& src, const SkMask& dst, SkMaskGamma::
uint16_t* dstP = (uint16_t*)dst.fImage;
size_t dstRB = dst.fRowBytes;
- const uint8_t* maskPreBlendR = NULL;
- const uint8_t* maskPreBlendG = NULL;
- const uint8_t* maskPreBlendB = NULL;
- if (APPLY_PREBLEND) {
- maskPreBlendR = maskPreBlend->fR;
- maskPreBlendG = maskPreBlend->fG;
- maskPreBlendB = maskPreBlend->fB;
- }
-
for (int y = 0; y < height; ++y) {
const uint8_t* srcP = src.getAddr8(0, y);
for (int x = 0; x < width; ++x) {
- U8CPU r = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlendR);
- U8CPU g = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlendG);
- U8CPU b = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlendB);
+ U8CPU r = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlend.fR);
+ U8CPU g = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlend.fG);
+ U8CPU b = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlend.fB);
dstP[x] = SkPack888ToRGB16(r, g, b);
}
dstP = (uint16_t*)((char*)dstP + dstRB);
@@ -389,7 +401,8 @@ static void pack3xHToLCD16(const SkBitmap& src, const SkMask& dst, SkMaskGamma::
}
template<bool APPLY_PREBLEND>
-static void pack3xHToLCD32(const SkBitmap& src, const SkMask& dst, SkMaskGamma::PreBlend* maskPreBlend) {
+static void pack3xHToLCD32(const SkBitmap& src, const SkMask& dst,
+ const SkMaskGamma::PreBlend& maskPreBlend) {
SkASSERT(SkBitmap::kA8_Config == src.config());
SkASSERT(SkMask::kLCD32_Format == dst.fFormat);
@@ -398,28 +411,20 @@ static void pack3xHToLCD32(const SkBitmap& src, const SkMask& dst, SkMaskGamma::
SkPMColor* dstP = (SkPMColor*)dst.fImage;
size_t dstRB = dst.fRowBytes;
- const uint8_t* maskPreBlendR = NULL;
- const uint8_t* maskPreBlendG = NULL;
- const uint8_t* maskPreBlendB = NULL;
- if (APPLY_PREBLEND) {
- maskPreBlendR = maskPreBlend->fR;
- maskPreBlendG = maskPreBlend->fG;
- maskPreBlendB = maskPreBlend->fB;
- }
-
for (int y = 0; y < height; ++y) {
const uint8_t* srcP = src.getAddr8(0, y);
for (int x = 0; x < width; ++x) {
- U8CPU r = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlendR);
- U8CPU g = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlendG);
- U8CPU b = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlendB);
+ U8CPU r = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlend.fR);
+ U8CPU g = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlend.fG);
+ U8CPU b = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlend.fB);
dstP[x] = SkPackARGB32(0xFF, r, g, b);
}
dstP = (SkPMColor*)((char*)dstP + dstRB);
}
}
-static void generateMask(const SkMask& mask, const SkPath& path, SkMaskGamma::PreBlend* maskPreBlend) {
+static void generateMask(const SkMask& mask, const SkPath& path,
+ const SkMaskGamma::PreBlend& maskPreBlend) {
SkBitmap::Config config;
SkPaint paint;
@@ -461,7 +466,11 @@ static void generateMask(const SkMask& mask, const SkPath& path, SkMaskGamma::Pr
bm.setConfig(config, dstW, dstH, dstRB);
if (0 == dstRB) {
- bm.allocPixels();
+ if (!bm.allocPixels()) {
+ // can't allocate offscreen, so empty the mask and return
+ sk_bzero(mask.fImage, mask.computeImageSize());
+ return;
+ }
bm.lockPixels();
} else {
bm.setPixels(mask.fImage);
@@ -475,44 +484,34 @@ static void generateMask(const SkMask& mask, const SkPath& path, SkMaskGamma::Pr
draw.fBitmap = &bm;
draw.drawPath(path, paint);
- if (0 == dstRB) {
- switch (mask.fFormat) {
- case SkMask::kLCD16_Format:
- if (maskPreBlend) {
- pack3xHToLCD16<true>(bm, mask, maskPreBlend);
- } else {
- pack3xHToLCD16<false>(bm, mask, maskPreBlend);
- }
- break;
- case SkMask::kLCD32_Format:
- if (maskPreBlend) {
- pack3xHToLCD32<true>(bm, mask, maskPreBlend);
- } else {
- pack3xHToLCD32<false>(bm, mask, maskPreBlend);
- }
- break;
- default:
- SkDEBUGFAIL("bad format for copyback");
- }
+ switch (mask.fFormat) {
+ case SkMask::kA8_Format:
+ if (maskPreBlend.isApplicable()) {
+ applyLUTToA8Mask(mask, maskPreBlend.fG);
+ }
+ break;
+ case SkMask::kLCD16_Format:
+ if (maskPreBlend.isApplicable()) {
+ pack3xHToLCD16<true>(bm, mask, maskPreBlend);
+ } else {
+ pack3xHToLCD16<false>(bm, mask, maskPreBlend);
+ }
+ break;
+ case SkMask::kLCD32_Format:
+ if (maskPreBlend.isApplicable()) {
+ pack3xHToLCD32<true>(bm, mask, maskPreBlend);
+ } else {
+ pack3xHToLCD32<false>(bm, mask, maskPreBlend);
+ }
+ break;
+ default:
+ break;
}
}
-static void applyLUTToA8Glyph(const SkGlyph& glyph, const uint8_t* lut) {
- uint8_t* SK_RESTRICT dst = (uint8_t*)glyph.fImage;
- unsigned rowBytes = glyph.rowBytes();
-
- for (int y = glyph.fHeight - 1; y >= 0; --y) {
- for (int x = glyph.fWidth - 1; x >= 0; --x) {
- dst[x] = lut[dst[x]];
- }
- dst += rowBytes;
- }
-}
-
void SkScalerContext::getImage(const SkGlyph& origGlyph) {
const SkGlyph* glyph = &origGlyph;
SkGlyph tmpGlyph;
- SkMaskGamma::PreBlend* maskPreBlend = fMaskPreBlend.fG ? &fMaskPreBlend : NULL;
if (fMaskFilter) { // restore the prefilter bounds
tmpGlyph.init(origGlyph.fID);
@@ -529,7 +528,6 @@ void SkScalerContext::getImage(const SkGlyph& origGlyph) {
SkASSERT(tmpGlyph.fWidth <= origGlyph.fWidth);
SkASSERT(tmpGlyph.fHeight <= origGlyph.fHeight);
glyph = &tmpGlyph;
- maskPreBlend = NULL;
}
if (fGenerateImageFromPath) {
@@ -549,19 +547,14 @@ void SkScalerContext::getImage(const SkGlyph& origGlyph) {
SkMask::kJustRenderImage_CreateMode)) {
return;
}
- //apply maskPreBlend to a8 (if not NULL)
- if (maskPreBlend) {
- applyLUTToA8Glyph(*glyph, maskPreBlend->fG);
+ if (fPreBlend.isApplicable()) {
+ applyLUTToA8Mask(mask, fPreBlend.fG);
}
} else {
- generateMask(mask, devPath, maskPreBlend);
- //apply maskPreBlend to a8 (if not NULL) -- already applied to lcd.
- if (maskPreBlend && mask.fFormat == SkMask::kA8_Format) {
- applyLUTToA8Glyph(*glyph, maskPreBlend->fG);
- }
+ generateMask(mask, devPath, fPreBlend);
}
} else {
- this->getGlyphContext(*glyph)->generateImage(*glyph, maskPreBlend);
+ this->getGlyphContext(*glyph)->generateImage(*glyph);
}
if (fMaskFilter) {
@@ -597,10 +590,9 @@ void SkScalerContext::getImage(const SkGlyph& origGlyph) {
}
SkMask::FreeImage(dstM.fImage);
- /* Pre-blend is not currently applied to filtered text.
- The primary filter is blur, for which contrast makes no sense,
- and for which the destination guess error is more visible. */
- //applyLUTToA8Glyph(origGlyph, fMaskPreBlend.fG);
+ if (fPreBlendForFilter.isApplicable()) {
+ applyLUTToA8Mask(srcM, fPreBlendForFilter.fG);
+ }
}
}
}
@@ -757,22 +749,22 @@ public:
SkScalerContext_Empty(const SkDescriptor* desc) : SkScalerContext(desc) {}
protected:
- virtual unsigned generateGlyphCount() {
+ virtual unsigned generateGlyphCount() SK_OVERRIDE {
return 0;
}
- virtual uint16_t generateCharToGlyph(SkUnichar uni) {
+ virtual uint16_t generateCharToGlyph(SkUnichar uni) SK_OVERRIDE {
return 0;
}
- virtual void generateAdvance(SkGlyph* glyph) {
+ virtual void generateAdvance(SkGlyph* glyph) SK_OVERRIDE {
glyph->zeroMetrics();
}
- virtual void generateMetrics(SkGlyph* glyph) {
+ virtual void generateMetrics(SkGlyph* glyph) SK_OVERRIDE {
glyph->zeroMetrics();
}
- virtual void generateImage(const SkGlyph& glyph, SkMaskGamma::PreBlend* maskPreBlend) {}
- virtual void generatePath(const SkGlyph& glyph, SkPath* path) {}
+ virtual void generateImage(const SkGlyph& glyph) SK_OVERRIDE {}
+ virtual void generatePath(const SkGlyph& glyph, SkPath* path) SK_OVERRIDE {}
virtual void generateFontMetrics(SkPaint::FontMetrics* mx,
- SkPaint::FontMetrics* my) {
+ SkPaint::FontMetrics* my) SK_OVERRIDE {
if (mx) {
sk_bzero(mx, sizeof(*mx));
}
diff --git a/src/core/SkScalerContext.h b/src/core/SkScalerContext.h
index 8c6d8e6b84..79f93e6c15 100644
--- a/src/core/SkScalerContext.h
+++ b/src/core/SkScalerContext.h
@@ -79,7 +79,7 @@ struct SkScalerContextRec {
* paint and device gamma to be effectively 1.0.
*/
void ignorePreBlend() {
- setLuminanceColor(0x00000000);
+ setLuminanceColor(SK_ColorTRANSPARENT);
setPaintGamma(SK_Scalar1);
setDeviceGamma(SK_Scalar1);
setContrast(0);
@@ -212,7 +212,7 @@ protected:
virtual uint16_t generateCharToGlyph(SkUnichar) = 0;
virtual void generateAdvance(SkGlyph*) = 0;
virtual void generateMetrics(SkGlyph*) = 0;
- virtual void generateImage(const SkGlyph&, SkMaskGamma::PreBlend* maskPreBlend) = 0;
+ virtual void generateImage(const SkGlyph&) = 0;
virtual void generatePath(const SkGlyph&, SkPath*) = 0;
virtual void generateFontMetrics(SkPaint::FontMetrics* mX,
SkPaint::FontMetrics* mY) = 0;
@@ -245,8 +245,14 @@ private:
// link-list of context, to handle missing chars. null-terminated.
SkScalerContext* fNextContext;
- // converts linear masks to gamma correcting masks.
- SkMaskGamma::PreBlend fMaskPreBlend;
+ // SkMaskGamma::PreBlend converts linear masks to gamma correcting masks.
+protected:
+ // Visible to subclasses so that generateImage can apply the pre-blend directly.
+ const SkMaskGamma::PreBlend fPreBlend;
+private:
+ // When there is a filter, previous steps must create a linear mask
+ // and the pre-blend applied as a final step.
+ const SkMaskGamma::PreBlend fPreBlendForFilter;
};
#define kRec_SkDescriptorTag SkSetFourByteTag('s', 'r', 'e', 'c')
diff --git a/src/core/SkScan_Antihair.cpp b/src/core/SkScan_Antihair.cpp
index 92245d4f33..bf1b81f158 100644
--- a/src/core/SkScan_Antihair.cpp
+++ b/src/core/SkScan_Antihair.cpp
@@ -86,45 +86,82 @@ static void call_hline_blitter(SkBlitter* blitter, int x, int y, int count,
} while (count > 0);
}
-static SkFixed hline(int x, int stopx, SkFixed fy, SkFixed /*slope*/,
- SkBlitter* blitter, int mod64) {
- SkASSERT(x < stopx);
- int count = stopx - x;
- fy += SK_Fixed1/2;
+class SkAntiHairBlitter {
+public:
+ SkAntiHairBlitter() : fBlitter(NULL) {}
- int y = fy >> 16;
- uint8_t a = (uint8_t)(fy >> 8);
+ SkBlitter* getBlitter() const { return fBlitter; }
- // lower line
- unsigned ma = SmallDot6Scale(a, mod64);
- if (ma) {
- call_hline_blitter(blitter, x, y, count, ma);
+ void setup(SkBlitter* blitter) {
+ fBlitter = blitter;
}
- // upper line
- ma = SmallDot6Scale(255 - a, mod64);
- if (ma) {
- call_hline_blitter(blitter, x, y - 1, count, ma);
+ virtual SkFixed drawCap(int x, SkFixed fy, SkFixed slope, int mod64) = 0;
+ virtual SkFixed drawLine(int x, int stopx, SkFixed fy, SkFixed slope) = 0;
+
+private:
+ SkBlitter* fBlitter;
+};
+
+class HLine_SkAntiHairBlitter : public SkAntiHairBlitter {
+public:
+ virtual SkFixed drawCap(int x, SkFixed fy, SkFixed slope, int mod64) SK_OVERRIDE {
+ fy += SK_Fixed1/2;
+
+ int y = fy >> 16;
+ uint8_t a = (uint8_t)(fy >> 8);
+
+ // lower line
+ unsigned ma = SmallDot6Scale(a, mod64);
+ if (ma) {
+ call_hline_blitter(this->getBlitter(), x, y, 1, ma);
+ }
+
+ // upper line
+ ma = SmallDot6Scale(255 - a, mod64);
+ if (ma) {
+ call_hline_blitter(this->getBlitter(), x, y - 1, 1, ma);
+ }
+
+ return fy - SK_Fixed1/2;
}
- return fy - SK_Fixed1/2;
-}
+ virtual SkFixed drawLine(int x, int stopx, SkFixed fy,
+ SkFixed slope) SK_OVERRIDE {
+ SkASSERT(x < stopx);
+ int count = stopx - x;
+ fy += SK_Fixed1/2;
-static SkFixed horish(int x, int stopx, SkFixed fy, SkFixed dy,
- SkBlitter* blitter, int mod64) {
- SkASSERT(x < stopx);
+ int y = fy >> 16;
+ uint8_t a = (uint8_t)(fy >> 8);
-#ifdef TEST_GAMMA
- const uint8_t* gamma = gGammaTable;
-#endif
- int16_t runs[2];
- uint8_t aa[1];
+ // lower line
+ if (a) {
+ call_hline_blitter(this->getBlitter(), x, y, count, a);
+ }
+
+ // upper line
+ a = 255 - a;
+ if (a) {
+ call_hline_blitter(this->getBlitter(), x, y - 1, count, a);
+ }
- runs[0] = 1;
- runs[1] = 0;
+ return fy - SK_Fixed1/2;
+ }
+};
+
+class Horish_SkAntiHairBlitter : public SkAntiHairBlitter {
+public:
+ virtual SkFixed drawCap(int x, SkFixed fy, SkFixed dy, int mod64) SK_OVERRIDE {
+ int16_t runs[2];
+ uint8_t aa[1];
+
+ runs[0] = 1;
+ runs[1] = 0;
+
+ fy += SK_Fixed1/2;
+ SkBlitter* blitter = this->getBlitter();
- fy += SK_Fixed1/2;
- do {
int lower_y = fy >> 16;
uint8_t a = (uint8_t)(fy >> 8);
unsigned ma = SmallDot6Scale(a, mod64);
@@ -144,64 +181,140 @@ static SkFixed horish(int x, int stopx, SkFixed fy, SkFixed dy,
SkASSERT(runs[1] == 0);
}
fy += dy;
- } while (++x < stopx);
-
- return fy - SK_Fixed1/2;
-}
-static SkFixed vline(int y, int stopy, SkFixed fx, SkFixed /*slope*/,
- SkBlitter* blitter, int mod64) {
- SkASSERT(y < stopy);
- fx += SK_Fixed1/2;
+ return fy - SK_Fixed1/2;
+ }
- int x = fx >> 16;
- int a = (uint8_t)(fx >> 8);
+ virtual SkFixed drawLine(int x, int stopx, SkFixed fy, SkFixed dy) SK_OVERRIDE {
+ SkASSERT(x < stopx);
+
+ int16_t runs[2];
+ uint8_t aa[1];
+
+ runs[0] = 1;
+ runs[1] = 0;
+
+ fy += SK_Fixed1/2;
+ SkBlitter* blitter = this->getBlitter();
+ do {
+ int lower_y = fy >> 16;
+ uint8_t a = (uint8_t)(fy >> 8);
+ if (a) {
+ aa[0] = a;
+ blitter->blitAntiH(x, lower_y, aa, runs);
+ // the clipping blitters might edit runs, but should not affect us
+ SkASSERT(runs[0] == 1);
+ SkASSERT(runs[1] == 0);
+ }
+ a = 255 - a;
+ if (a) {
+ aa[0] = a;
+ blitter->blitAntiH(x, lower_y - 1, aa, runs);
+ // the clipping blitters might edit runs, but should not affect us
+ SkASSERT(runs[0] == 1);
+ SkASSERT(runs[1] == 0);
+ }
+ fy += dy;
+ } while (++x < stopx);
- unsigned ma = SmallDot6Scale(a, mod64);
- if (ma) {
- blitter->blitV(x, y, stopy - y, ApplyGamma(gGammaTable, ma));
+ return fy - SK_Fixed1/2;
}
- ma = SmallDot6Scale(255 - a, mod64);
- if (ma) {
- blitter->blitV(x - 1, y, stopy - y, ApplyGamma(gGammaTable, ma));
+};
+
+class VLine_SkAntiHairBlitter : public SkAntiHairBlitter {
+public:
+ virtual SkFixed drawCap(int y, SkFixed fx, SkFixed dx, int mod64) SK_OVERRIDE {
+ SkASSERT(0 == dx);
+ fx += SK_Fixed1/2;
+
+ int x = fx >> 16;
+ int a = (uint8_t)(fx >> 8);
+
+ unsigned ma = SmallDot6Scale(a, mod64);
+ if (ma) {
+ this->getBlitter()->blitV(x, y, 1, ma);
+ }
+ ma = SmallDot6Scale(255 - a, mod64);
+ if (ma) {
+ this->getBlitter()->blitV(x - 1, y, 1, ma);
+ }
+
+ return fx - SK_Fixed1/2;
}
- return fx - SK_Fixed1/2;
-}
+ virtual SkFixed drawLine(int y, int stopy, SkFixed fx, SkFixed dx) SK_OVERRIDE {
+ SkASSERT(y < stopy);
+ SkASSERT(0 == dx);
+ fx += SK_Fixed1/2;
-static SkFixed vertish(int y, int stopy, SkFixed fx, SkFixed dx,
- SkBlitter* blitter, int mod64) {
- SkASSERT(y < stopy);
-#ifdef TEST_GAMMA
- const uint8_t* gamma = gGammaTable;
-#endif
- int16_t runs[3];
- uint8_t aa[2];
+ int x = fx >> 16;
+ int a = (uint8_t)(fx >> 8);
- runs[0] = 1;
- runs[2] = 0;
+ if (a) {
+ this->getBlitter()->blitV(x, y, stopy - y, a);
+ }
+ a = 255 - a;
+ if (a) {
+ this->getBlitter()->blitV(x - 1, y, stopy - y, a);
+ }
- fx += SK_Fixed1/2;
- do {
+ return fx - SK_Fixed1/2;
+ }
+};
+
+class Vertish_SkAntiHairBlitter : public SkAntiHairBlitter {
+public:
+ virtual SkFixed drawCap(int y, SkFixed fx, SkFixed dx, int mod64) SK_OVERRIDE {
+ int16_t runs[3];
+ uint8_t aa[2];
+
+ runs[0] = 1;
+ runs[2] = 0;
+
+ fx += SK_Fixed1/2;
int x = fx >> 16;
uint8_t a = (uint8_t)(fx >> 8);
- aa[0] = ApplyGamma(gamma, SmallDot6Scale(255 - a, mod64));
- aa[1] = ApplyGamma(gamma, SmallDot6Scale(a, mod64));
+ aa[0] = SmallDot6Scale(255 - a, mod64);
+ aa[1] = SmallDot6Scale(a, mod64);
// the clippng blitters might overwrite this guy, so we have to reset it each time
runs[1] = 1;
- blitter->blitAntiH(x - 1, y, aa, runs);
+ this->getBlitter()->blitAntiH(x - 1, y, aa, runs);
// the clipping blitters might edit runs, but should not affect us
SkASSERT(runs[0] == 1);
SkASSERT(runs[2] == 0);
fx += dx;
- } while (++y < stopy);
- return fx - SK_Fixed1/2;
-}
+ return fx - SK_Fixed1/2;
+ }
+
+ virtual SkFixed drawLine(int y, int stopy, SkFixed fx, SkFixed dx) SK_OVERRIDE {
+ SkASSERT(y < stopy);
+ int16_t runs[3];
+ uint8_t aa[2];
-typedef SkFixed (*LineProc)(int istart, int istop, SkFixed fstart,
- SkFixed slope, SkBlitter*, int);
+ runs[0] = 1;
+ runs[2] = 0;
+
+ fx += SK_Fixed1/2;
+ do {
+ int x = fx >> 16;
+ uint8_t a = (uint8_t)(fx >> 8);
+
+ aa[0] = 255 - a;
+ aa[1] = a;
+ // the clippng blitters might overwrite this guy, so we have to reset it each time
+ runs[1] = 1;
+ this->getBlitter()->blitAntiH(x - 1, y, aa, runs);
+ // the clipping blitters might edit runs, but should not affect us
+ SkASSERT(runs[0] == 1);
+ SkASSERT(runs[2] == 0);
+ fx += dx;
+ } while (++y < stopy);
+
+ return fx - SK_Fixed1/2;
+ }
+};
static inline SkFixed fastfixdiv(SkFDot6 a, SkFDot6 b) {
SkASSERT((a << 16 >> 16) == a);
@@ -235,6 +348,25 @@ static bool canConvertFDot6ToFixed(SkFDot6 x) {
return SkAbs32(x) <= maxDot6;
}
+/*
+ * We want the fractional part of ordinate, but we want multiples of 64 to
+ * return 64, not 0, so we can't just say (ordinate & 63).
+ * We basically want to compute those bits, and if they're 0, return 64.
+ * We can do that w/o a branch with an extra sub and add.
+ */
+static int contribution_64(SkFDot6 ordinate) {
+#if 0
+ int result = ordinate & 63;
+ if (0 == result) {
+ result = 64;
+ }
+#else
+ int result = ((ordinate - 1) & 63) + 1;
+#endif
+ SkASSERT(result > 0 && result <= 64);
+ return result;
+}
+
static void do_anti_hairline(SkFDot6 x0, SkFDot6 y0, SkFDot6 x1, SkFDot6 y1,
const SkIRect* clip, SkBlitter* blitter) {
// check for integer NaN (0x80000000) which we can't handle (can't negate it)
@@ -268,7 +400,12 @@ static void do_anti_hairline(SkFDot6 x0, SkFDot6 y0, SkFDot6 x1, SkFDot6 y1,
int scaleStart, scaleStop;
int istart, istop;
SkFixed fstart, slope;
- LineProc proc;
+
+ HLine_SkAntiHairBlitter hline_blitter;
+ Horish_SkAntiHairBlitter horish_blitter;
+ VLine_SkAntiHairBlitter vline_blitter;
+ Vertish_SkAntiHairBlitter vertish_blitter;
+ SkAntiHairBlitter* hairBlitter = NULL;
if (SkAbs32(x1 - x0) > SkAbs32(y1 - y0)) { // mostly horizontal
if (x0 > x1) { // we want to go left-to-right
@@ -281,16 +418,17 @@ static void do_anti_hairline(SkFDot6 x0, SkFDot6 y0, SkFDot6 x1, SkFDot6 y1,
fstart = SkFDot6ToFixed(y0);
if (y0 == y1) { // completely horizontal, take fast case
slope = 0;
- proc = hline;
+ hairBlitter = &hline_blitter;
} else {
slope = fastfixdiv(y1 - y0, x1 - x0);
SkASSERT(slope >= -SK_Fixed1 && slope <= SK_Fixed1);
fstart += (slope * (32 - (x0 & 63)) + 32) >> 6;
- proc = horish;
+ hairBlitter = &horish_blitter;
}
SkASSERT(istop > istart);
if (istop - istart == 1) {
+ // we are within a single pixel
scaleStart = x1 - x0;
SkASSERT(scaleStart >= 0 && scaleStart <= 64);
scaleStop = 0;
@@ -307,6 +445,11 @@ static void do_anti_hairline(SkFDot6 x0, SkFDot6 y0, SkFDot6 x1, SkFDot6 y1,
fstart += slope * (clip->fLeft - istart);
istart = clip->fLeft;
scaleStart = 64;
+ if (istop - istart == 1) {
+ // we are within a single pixel
+ scaleStart = contribution_64(x1);
+ scaleStop = 0;
+ }
}
if (istop > clip->fRight) {
istop = clip->fRight;
@@ -351,16 +494,17 @@ static void do_anti_hairline(SkFDot6 x0, SkFDot6 y0, SkFDot6 x1, SkFDot6 y1,
return; // nothing to do
}
slope = 0;
- proc = vline;
+ hairBlitter = &vline_blitter;
} else {
slope = fastfixdiv(x1 - x0, y1 - y0);
SkASSERT(slope <= SK_Fixed1 && slope >= -SK_Fixed1);
fstart += (slope * (32 - (y0 & 63)) + 32) >> 6;
- proc = vertish;
+ hairBlitter = &vertish_blitter;
}
SkASSERT(istop > istart);
if (istop - istart == 1) {
+ // we are within a single pixel
scaleStart = y1 - y0;
SkASSERT(scaleStart >= 0 && scaleStart <= 64);
scaleStop = 0;
@@ -377,6 +521,11 @@ static void do_anti_hairline(SkFDot6 x0, SkFDot6 y0, SkFDot6 x1, SkFDot6 y1,
fstart += slope * (clip->fTop - istart);
istart = clip->fTop;
scaleStart = 64;
+ if (istop - istart == 1) {
+ // we are within a single pixel
+ scaleStart = contribution_64(y1);
+ scaleStop = 0;
+ }
}
if (istop > clip->fBottom) {
istop = clip->fBottom;
@@ -415,14 +564,24 @@ static void do_anti_hairline(SkFDot6 x0, SkFDot6 y0, SkFDot6 x1, SkFDot6 y1,
blitter = &rectClipper;
}
- fstart = proc(istart, istart + 1, fstart, slope, blitter, scaleStart);
+ SkASSERT(hairBlitter);
+ hairBlitter->setup(blitter);
+
+#ifdef SK_DEBUG
+ if (scaleStart > 0 && scaleStop > 0) {
+ // be sure we don't draw twice in the same pixel
+ SkASSERT(istart < istop - 1);
+ }
+#endif
+
+ fstart = hairBlitter->drawCap(istart, fstart, slope, scaleStart);
istart += 1;
int fullSpans = istop - istart - (scaleStop > 0);
if (fullSpans > 0) {
- fstart = proc(istart, istart + fullSpans, fstart, slope, blitter, 64);
+ fstart = hairBlitter->drawLine(istart, istart + fullSpans, fstart, slope);
}
if (scaleStop > 0) {
- proc(istop - 1, istop, fstart, slope, blitter, scaleStop);
+ hairBlitter->drawCap(istop - 1, fstart, slope, scaleStop);
}
}
diff --git a/src/core/SkScan_Path.cpp b/src/core/SkScan_Path.cpp
index 68ac7ff51f..a68f2a74f9 100644
--- a/src/core/SkScan_Path.cpp
+++ b/src/core/SkScan_Path.cpp
@@ -219,9 +219,6 @@ static bool update_edge(SkEdge* edge, int last_y) {
static void walk_convex_edges(SkEdge* prevHead, SkPath::FillType,
SkBlitter* blitter, int start_y, int stop_y,
PrePostProc proc) {
- static int gCalls;
- gCalls++;
-
validate_sort(prevHead->fNext);
SkEdge* leftE = prevHead->fNext;
@@ -238,10 +235,7 @@ static void walk_convex_edges(SkEdge* prevHead, SkPath::FillType,
#endif
SkASSERT(local_top >= start_y);
- int gLoops = 0;
for (;;) {
- gLoops++;
-
SkASSERT(leftE->fFirstY <= stop_y);
SkASSERT(riteE->fFirstY <= stop_y);
diff --git a/src/core/SkShader.cpp b/src/core/SkShader.cpp
index 7e3a92bc57..706c1b79f5 100644
--- a/src/core/SkShader.cpp
+++ b/src/core/SkShader.cpp
@@ -15,94 +15,62 @@
SK_DEFINE_INST_COUNT(SkShader)
-SkShader::SkShader() : fLocalMatrix(NULL) {
- SkDEBUGCODE(fInSession = false;)
+SkShader::SkShader() {
+ fLocalMatrix.reset();
+ SkDEBUGCODE(fInSetContext = false;)
}
SkShader::SkShader(SkFlattenableReadBuffer& buffer)
- : INHERITED(buffer), fLocalMatrix(NULL) {
+ : INHERITED(buffer) {
if (buffer.readBool()) {
- SkMatrix matrix;
- buffer.readMatrix(&matrix);
- setLocalMatrix(matrix);
+ buffer.readMatrix(&fLocalMatrix);
+ } else {
+ fLocalMatrix.reset();
}
- SkDEBUGCODE(fInSession = false;)
-}
-SkShader::~SkShader() {
- SkASSERT(!fInSession);
- sk_free(fLocalMatrix);
+ SkDEBUGCODE(fInSetContext = false;)
}
-void SkShader::beginSession() {
- SkASSERT(!fInSession);
- SkDEBUGCODE(fInSession = true;)
-}
-
-void SkShader::endSession() {
- SkASSERT(fInSession);
- SkDEBUGCODE(fInSession = false;)
+SkShader::~SkShader() {
+ SkASSERT(!fInSetContext);
}
void SkShader::flatten(SkFlattenableWriteBuffer& buffer) const {
this->INHERITED::flatten(buffer);
- buffer.writeBool(fLocalMatrix != NULL);
- if (fLocalMatrix) {
- buffer.writeMatrix(*fLocalMatrix);
- }
-}
-
-bool SkShader::getLocalMatrix(SkMatrix* localM) const {
- if (fLocalMatrix) {
- if (localM) {
- *localM = *fLocalMatrix;
- }
- return true;
- } else {
- if (localM) {
- localM->reset();
- }
- return false;
- }
-}
-
-void SkShader::setLocalMatrix(const SkMatrix& localM) {
- if (localM.isIdentity()) {
- this->resetLocalMatrix();
- } else {
- if (fLocalMatrix == NULL) {
- fLocalMatrix = (SkMatrix*)sk_malloc_throw(sizeof(SkMatrix));
- }
- *fLocalMatrix = localM;
- }
-}
-
-void SkShader::resetLocalMatrix() {
- if (fLocalMatrix) {
- sk_free(fLocalMatrix);
- fLocalMatrix = NULL;
+ bool hasLocalM = this->hasLocalMatrix();
+ buffer.writeBool(hasLocalM);
+ if (hasLocalM) {
+ buffer.writeMatrix(fLocalMatrix);
}
}
bool SkShader::setContext(const SkBitmap& device,
const SkPaint& paint,
const SkMatrix& matrix) {
+ SkASSERT(!this->setContextHasBeenCalled());
+
const SkMatrix* m = &matrix;
SkMatrix total;
fDeviceConfig = SkToU8(device.getConfig());
fPaintAlpha = paint.getAlpha();
- if (fLocalMatrix) {
- total.setConcat(matrix, *fLocalMatrix);
+ if (this->hasLocalMatrix()) {
+ total.setConcat(matrix, this->getLocalMatrix());
m = &total;
}
if (m->invert(&fTotalInverse)) {
fTotalInverseClass = (uint8_t)ComputeMatrixClass(fTotalInverse);
+ SkDEBUGCODE(fInSetContext = true;)
return true;
}
return false;
}
+void SkShader::endContext() {
+ SkASSERT(fInSetContext);
+ SkDEBUGCODE(fInSetContext = false;)
+}
+
SkShader::ShadeProc SkShader::asAShadeProc(void** ctx) {
return NULL;
}
@@ -205,7 +173,7 @@ SkShader::GradientType SkShader::asAGradient(GradientInfo* info) const {
return kNone_GradientType;
}
-bool SkShader::asNewCustomStage(GrContext*, GrSamplerState*) const {
+bool SkShader::asNewEffect(GrContext*, GrEffectStage*) const {
return false;
}
diff --git a/src/core/SkString.cpp b/src/core/SkString.cpp
index fee614cc03..5d1b8ceea3 100644
--- a/src/core/SkString.cpp
+++ b/src/core/SkString.cpp
@@ -36,13 +36,23 @@ static const size_t kBufferSize = 512;
///////////////////////////////////////////////////////////////////////////////
-bool SkStrEndsWith(const char string[], const char suffix[]) {
+bool SkStrEndsWith(const char string[], const char suffixStr[]) {
SkASSERT(string);
- SkASSERT(suffix);
+ SkASSERT(suffixStr);
size_t strLen = strlen(string);
- size_t suffixLen = strlen(suffix);
+ size_t suffixLen = strlen(suffixStr);
return strLen >= suffixLen &&
- !strncmp(string + strLen - suffixLen, suffix, suffixLen);
+ !strncmp(string + strLen - suffixLen, suffixStr, suffixLen);
+}
+
+bool SkStrEndsWith(const char string[], const char suffixChar) {
+ SkASSERT(string);
+ size_t strLen = strlen(string);
+ if (0 == strLen) {
+ return false;
+ } else {
+ return (suffixChar == string[strLen-1]);
+ }
}
int SkStrStartsWithOneOf(const char string[], const char prefixes[]) {
@@ -602,4 +612,3 @@ SkString SkStringPrintf(const char* format, ...) {
#undef VSNPRINTF
#undef SNPRINTF
-
diff --git a/src/core/SkStroke.cpp b/src/core/SkStroke.cpp
index 0ab1016a46..e45a4d0f0a 100644
--- a/src/core/SkStroke.cpp
+++ b/src/core/SkStroke.cpp
@@ -1,4 +1,3 @@
-
/*
* Copyright 2008 The Android Open Source Project
*
@@ -6,7 +5,6 @@
* found in the LICENSE file.
*/
-
#include "SkStrokerPriv.h"
#include "SkGeometry.h"
#include "SkPath.h"
@@ -609,6 +607,21 @@ void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
return;
}
+ // If src is really a rect, call our specialty strokeRect() method
+ {
+ bool isClosed;
+ SkPath::Direction dir;
+ if (src.isRect(&isClosed, &dir) && isClosed) {
+ this->strokeRect(src.getBounds(), dst, dir);
+ // our answer should preserve the inverseness of the src
+ if (src.isInverseFillType()) {
+ SkASSERT(!dst->isInverseFillType());
+ dst->toggleInverseFillType();
+ }
+ return;
+ }
+ }
+
#ifdef SK_SCALAR_IS_FIXED
void (*proc)(SkPoint pts[], int count) = identity_proc;
if (needs_to_shrink(src)) {
@@ -702,3 +715,83 @@ void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
}
}
+static SkPath::Direction reverse_direction(SkPath::Direction dir) {
+ SkASSERT(SkPath::kUnknown_Direction != dir);
+ return SkPath::kCW_Direction == dir ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
+}
+
+static void addBevel(SkPath* path, const SkRect& r, const SkRect& outer, SkPath::Direction dir) {
+ SkPoint pts[8];
+
+ if (SkPath::kCW_Direction == dir) {
+ pts[0].set(r.fLeft, outer.fTop);
+ pts[1].set(r.fRight, outer.fTop);
+ pts[2].set(outer.fRight, r.fTop);
+ pts[3].set(outer.fRight, r.fBottom);
+ pts[4].set(r.fRight, outer.fBottom);
+ pts[5].set(r.fLeft, outer.fBottom);
+ pts[6].set(outer.fLeft, r.fBottom);
+ pts[7].set(outer.fLeft, r.fTop);
+ } else {
+ pts[7].set(r.fLeft, outer.fTop);
+ pts[6].set(r.fRight, outer.fTop);
+ pts[5].set(outer.fRight, r.fTop);
+ pts[4].set(outer.fRight, r.fBottom);
+ pts[3].set(r.fRight, outer.fBottom);
+ pts[2].set(r.fLeft, outer.fBottom);
+ pts[1].set(outer.fLeft, r.fBottom);
+ pts[0].set(outer.fLeft, r.fTop);
+ }
+ path->addPoly(pts, 8, true);
+}
+
+void SkStroke::strokeRect(const SkRect& origRect, SkPath* dst,
+ SkPath::Direction dir) const {
+ SkASSERT(dst != NULL);
+ dst->reset();
+
+ SkScalar radius = SkScalarHalf(fWidth);
+ if (radius <= 0) {
+ return;
+ }
+
+ SkScalar rw = origRect.width();
+ SkScalar rh = origRect.height();
+ if ((rw < 0) ^ (rh < 0)) {
+ dir = reverse_direction(dir);
+ }
+ SkRect rect(origRect);
+ rect.sort();
+ // reassign these, now that we know they'll be >= 0
+ rw = rect.width();
+ rh = rect.height();
+
+ SkRect r(rect);
+ r.outset(radius, radius);
+
+ SkPaint::Join join = (SkPaint::Join)fJoin;
+ if (SkPaint::kMiter_Join == join && fMiterLimit < SK_ScalarSqrt2) {
+ join = SkPaint::kBevel_Join;
+ }
+
+ switch (join) {
+ case SkPaint::kMiter_Join:
+ dst->addRect(r, dir);
+ break;
+ case SkPaint::kBevel_Join:
+ addBevel(dst, rect, r, dir);
+ break;
+ case SkPaint::kRound_Join:
+ dst->addRoundRect(r, radius, radius, dir);
+ break;
+ default:
+ break;
+ }
+
+ if (fWidth < SkMinScalar(rw, rh) && !fDoFill) {
+ r = rect;
+ r.inset(radius, radius);
+ dst->addRect(r, reverse_direction(dir));
+ }
+}
+
diff --git a/src/core/SkStroke.h b/src/core/SkStroke.h
index 46e6ba1401..48805165cb 100644
--- a/src/core/SkStroke.h
+++ b/src/core/SkStroke.h
@@ -1,4 +1,3 @@
-
/*
* Copyright 2006 The Android Open Source Project
*
@@ -6,16 +5,13 @@
* found in the LICENSE file.
*/
-
#ifndef SkStroke_DEFINED
#define SkStroke_DEFINED
+#include "SkPath.h"
#include "SkPoint.h"
#include "SkPaint.h"
-struct SkRect;
-class SkPath;
-
/** \class SkStroke
SkStroke is the utility class that constructs paths by stroking
geometries (lines, rects, ovals, roundrects, paths). This is
@@ -40,6 +36,11 @@ public:
bool getDoFill() const { return SkToBool(fDoFill); }
void setDoFill(bool doFill) { fDoFill = SkToU8(doFill); }
+ /**
+ * Stroke the specified rect, winding it in the specified direction..
+ */
+ void strokeRect(const SkRect& rect, SkPath* result,
+ SkPath::Direction = SkPath::kCW_Direction) const;
void strokePath(const SkPath& path, SkPath*) const;
////////////////////////////////////////////////////////////////
diff --git a/src/core/SkStrokeRec.cpp b/src/core/SkStrokeRec.cpp
new file mode 100644
index 0000000000..756872bdfb
--- /dev/null
+++ b/src/core/SkStrokeRec.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkStrokeRec.h"
+#include "SkPaintDefaults.h"
+
+// must be < 0, since ==0 means hairline, and >0 means normal stroke
+#define kStrokeRec_FillStyleWidth (-SK_Scalar1)
+
+SkStrokeRec::SkStrokeRec(InitStyle s) {
+ fWidth = (kFill_InitStyle == s) ? kStrokeRec_FillStyleWidth : 0;
+ fMiterLimit = SkPaintDefaults_MiterLimit;
+ fCap = SkPaint::kDefault_Cap;
+ fJoin = SkPaint::kDefault_Join;
+ fStrokeAndFill = false;
+}
+
+SkStrokeRec::SkStrokeRec(const SkStrokeRec& src) {
+ memcpy(this, &src, sizeof(src));
+}
+
+SkStrokeRec::SkStrokeRec(const SkPaint& paint) {
+ switch (paint.getStyle()) {
+ case SkPaint::kFill_Style:
+ fWidth = kStrokeRec_FillStyleWidth;
+ fStrokeAndFill = false;
+ break;
+ case SkPaint::kStroke_Style:
+ fWidth = paint.getStrokeWidth();
+ fStrokeAndFill = false;
+ break;
+ case SkPaint::kStrokeAndFill_Style:
+ if (0 == paint.getStrokeWidth()) {
+ // hairline+fill == fill
+ fWidth = kStrokeRec_FillStyleWidth;
+ fStrokeAndFill = false;
+ } else {
+ fWidth = paint.getStrokeWidth();
+ fStrokeAndFill = true;
+ }
+ break;
+ default:
+ SkASSERT(!"unknown paint style");
+ // fall back on just fill
+ fWidth = kStrokeRec_FillStyleWidth;
+ fStrokeAndFill = false;
+ break;
+ }
+
+ // copy these from the paint, regardless of our "style"
+ fMiterLimit = paint.getStrokeMiter();
+ fCap = paint.getStrokeCap();
+ fJoin = paint.getStrokeJoin();
+}
+
+SkStrokeRec::Style SkStrokeRec::getStyle() const {
+ if (fWidth < 0) {
+ return kFill_Style;
+ } else if (0 == fWidth) {
+ return kHairline_Style;
+ } else {
+ return fStrokeAndFill ? kStrokeAndFill_Style : kStroke_Style;
+ }
+}
+
+void SkStrokeRec::setFillStyle() {
+ fWidth = kStrokeRec_FillStyleWidth;
+ fStrokeAndFill = false;
+}
+
+void SkStrokeRec::setHairlineStyle() {
+ fWidth = 0;
+ fStrokeAndFill = false;
+}
+
+void SkStrokeRec::setStrokeStyle(SkScalar width, bool strokeAndFill) {
+ if (strokeAndFill && (0 == width)) {
+ // hairline+fill == fill
+ this->setFillStyle();
+ } else {
+ fWidth = width;
+ fStrokeAndFill = strokeAndFill;
+ }
+}
+
+#include "SkStroke.h"
+
+bool SkStrokeRec::applyToPath(SkPath* dst, const SkPath& src) const {
+ if (fWidth <= 0) { // hairline or fill
+ return false;
+ }
+
+ SkStroke stroker;
+ stroker.setCap(fCap);
+ stroker.setJoin(fJoin);
+ stroker.setMiterLimit(fMiterLimit);
+ stroker.setWidth(fWidth);
+ stroker.setDoFill(fStrokeAndFill);
+ stroker.strokePath(src, dst);
+ return true;
+}
+
diff --git a/src/core/SkTLList.h b/src/core/SkTLList.h
new file mode 100644
index 0000000000..298ce516cc
--- /dev/null
+++ b/src/core/SkTLList.h
@@ -0,0 +1,385 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkTLList_DEFINED
+#define SkTLList_DEFINED
+
+#include "SkTInternalLList.h"
+#include "SkTemplates.h"
+
+template <typename T> class SkTLList;
+template <typename T>
+inline void* operator new(size_t, SkTLList<T>* list,
+ typename SkTLList<T>::Placement placement,
+ const typename SkTLList<T>::Iter& location);
+
+/** Doubly-linked list of objects. The objects' lifetimes are controlled by the list. I.e. the
+ the list creates the objects and they are deleted upon removal. This class block-allocates
+ space for entries based on a param passed to the constructor.
+
+ Elements of the list can be constructed in place using the following macros:
+ SkNEW_INSERT_IN_LLIST_BEFORE(list, location, type_name, args)
+ SkNEW_INSERT_IN_LLIST_AFTER(list, location, type_name, args)
+ where list is a SkTLList<type_name>*, location is an iterator, and args is the paren-surrounded
+ constructor arguments for type_name. These macros behave like addBefore() and addAfter().
+*/
+template <typename T>
+class SkTLList : public SkNoncopyable {
+private:
+ struct Block;
+ struct Node {
+ char fObj[sizeof(T)];
+ SK_DECLARE_INTERNAL_LLIST_INTERFACE(Node);
+ Block* fBlock; // owning block.
+ };
+ typedef SkTInternalLList<Node> NodeList;
+
+public:
+
+ class Iter;
+
+ /** allocCnt is the number of objects to allocate as a group. In the worst case fragmentation
+ each object is using the space required for allocCnt unfragmented objects. */
+ SkTLList(int allocCnt = 1) : fCount(0), fAllocCnt(allocCnt) {
+ SkASSERT(allocCnt > 0);
+ this->validate();
+ }
+
+ ~SkTLList() {
+ this->validate();
+ typename NodeList::Iter iter;
+ Node* node = iter.init(fList, Iter::kHead_IterStart);
+ while (NULL != node) {
+ SkTCast<T*>(node->fObj)->~T();
+ Block* block = node->fBlock;
+ node = iter.next();
+ if (0 == --block->fNodesInUse) {
+ for (int i = 0; i < fAllocCnt; ++i) {
+ block->fNodes[i].~Node();
+ }
+ sk_free(block);
+ }
+ }
+ }
+
+ T* addToHead(const T& t) {
+ this->validate();
+ Node* node = this->createNode();
+ fList.addToHead(node);
+ SkNEW_PLACEMENT_ARGS(node->fObj, T, (t));
+ this->validate();
+ return reinterpret_cast<T*>(node->fObj);
+ }
+
+ T* addToTail(const T& t) {
+ this->validate();
+ Node* node = this->createNode();
+ fList.addToTail(node);
+ SkNEW_PLACEMENT_ARGS(node->fObj, T, (t));
+ this->validate();
+ return reinterpret_cast<T*>(node->fObj);
+ }
+
+ /** Adds a new element to the list before the location indicated by the iterator. If the
+ iterator refers to a NULL location then the new element is added at the tail */
+ T* addBefore(const T& t, const Iter& location) {
+ return SkNEW_PLACEMENT_ARGS(this->internalAddBefore(location), T, (t));
+ }
+
+ /** Adds a new element to the list after the location indicated by the iterator. If the
+ iterator refers to a NULL location then the new element is added at the head */
+ T* addAfter(const T& t, const Iter& location) {
+ return SkNEW_PLACEMENT_ARGS(this->internalAddAfter(location), T, (t));
+ }
+
+ /** Convenience methods for getting an iterator initialized to the head/tail of the list. */
+ Iter headIter() const { return Iter(*this, Iter::kHead_IterStart); }
+ Iter tailIter() const { return Iter(*this, Iter::kTail_IterStart); }
+
+ T* head() { return Iter(*this, Iter::kHead_IterStart).get(); }
+ T* tail() { return Iter(*this, Iter::kTail_IterStart).get(); }
+ const T* head() const { return Iter(*this, Iter::kHead_IterStart).get(); }
+ const T* tail() const { return Iter(*this, Iter::kTail_IterStart).get(); }
+
+ void popHead() {
+ this->validate();
+ Node* node = fList.head();
+ if (NULL != node) {
+ this->removeNode(node);
+ }
+ this->validate();
+ }
+
+ void popTail() {
+ this->validate();
+ Node* node = fList.head();
+ if (NULL != node) {
+ this->removeNode(node);
+ }
+ this->validate();
+ }
+
+ void remove(T* t) {
+ this->validate();
+ Node* node = reinterpret_cast<Node*>(t);
+ SkASSERT(reinterpret_cast<T*>(node->fObj) == t);
+ this->removeNode(node);
+ this->validate();
+ }
+
+ void reset() {
+ this->validate();
+ Iter iter(*this, Iter::kHead_IterStart);
+ while (iter.get()) {
+ Iter next = iter;
+ next.next();
+ this->remove(iter.get());
+ iter = next;
+ }
+ SkASSERT(0 == fCount);
+ this->validate();
+ }
+
+ int count() const { return fCount; }
+ bool isEmpty() const { this->validate(); return 0 == fCount; }
+
+ bool operator== (const SkTLList& list) const {
+ if (this == &list) {
+ return true;
+ }
+ if (fCount != list.fCount) {
+ return false;
+ }
+ for (Iter a(*this, Iter::kHead_IterStart), b(list, Iter::kHead_IterStart);
+ a.get();
+ a.next(), b.next()) {
+ SkASSERT(NULL != b.get()); // already checked that counts match.
+ if (!(*a.get() == *b.get())) {
+ return false;
+ }
+ }
+ return true;
+ }
+ bool operator!= (const SkTLList& list) const { return !(*this == list); }
+
+ /** The iterator becomes invalid if the element it refers to is removed from the list. */
+ class Iter : private NodeList::Iter {
+ private:
+ typedef typename NodeList::Iter INHERITED;
+
+ public:
+ typedef typename INHERITED::IterStart IterStart;
+ //!< Start the iterator at the head of the list.
+ static const IterStart kHead_IterStart = INHERITED::kHead_IterStart;
+ //!< Start the iterator at the tail of the list.
+ static const IterStart kTail_IterStart = INHERITED::kTail_IterStart;
+
+ Iter() {}
+
+ Iter(const SkTLList& list, IterStart start) {
+ INHERITED::init(list.fList, start);
+ }
+
+ T* init(const SkTLList& list, IterStart start) {
+ return this->nodeToObj(INHERITED::init(list.fList, start));
+ }
+
+ T* get() { return this->nodeToObj(INHERITED::get()); }
+
+ T* next() { return this->nodeToObj(INHERITED::next()); }
+
+ T* prev() { return this->nodeToObj(INHERITED::prev()); }
+
+ Iter& operator= (const Iter& iter) { INHERITED::operator=(iter); return *this; }
+
+ private:
+ friend class SkTLList;
+ Node* getNode() { return INHERITED::get(); }
+
+ T* nodeToObj(Node* node) {
+ if (NULL != node) {
+ return reinterpret_cast<T*>(node->fObj);
+ } else {
+ return NULL;
+ }
+ }
+ };
+
+ // For use with operator new
+ enum Placement {
+ kBefore_Placement,
+ kAfter_Placement,
+ };
+
+private:
+ struct Block {
+ int fNodesInUse;
+ Node fNodes[1];
+ };
+
+ size_t blockSize() const { return sizeof(Block) + sizeof(Node) * (fAllocCnt-1); }
+
+ Node* createNode() {
+ Node* node = fFreeList.head();
+ if (NULL != node) {
+ fFreeList.remove(node);
+ ++node->fBlock->fNodesInUse;
+ } else {
+ Block* block = reinterpret_cast<Block*>(sk_malloc_flags(this->blockSize(), 0));
+ node = &block->fNodes[0];
+ SkNEW_PLACEMENT(node, Node);
+ node->fBlock = block;
+ block->fNodesInUse = 1;
+ for (int i = 1; i < fAllocCnt; ++i) {
+ SkNEW_PLACEMENT(block->fNodes + i, Node);
+ fFreeList.addToHead(block->fNodes + i);
+ block->fNodes[i].fBlock = block;
+ }
+ }
+ ++fCount;
+ return node;
+ }
+
+ void removeNode(Node* node) {
+ SkASSERT(NULL != node);
+ fList.remove(node);
+ SkTCast<T*>(node->fObj)->~T();
+ if (0 == --node->fBlock->fNodesInUse) {
+ Block* block = node->fBlock;
+ for (int i = 0; i < fAllocCnt; ++i) {
+ if (block->fNodes + i != node) {
+ fFreeList.remove(block->fNodes + i);
+ }
+ block->fNodes[i].~Node();
+ }
+ sk_free(block);
+ } else {
+ fFreeList.addToHead(node);
+ }
+ --fCount;
+ this->validate();
+ }
+
+ void validate() const {
+#ifdef SK_DEBUG
+ SkASSERT((0 == fCount) == fList.isEmpty());
+ SkASSERT((0 != fCount) || fFreeList.isEmpty());
+
+ fList.validate();
+ fFreeList.validate();
+ typename NodeList::Iter iter;
+ Node* freeNode = iter.init(fFreeList, Iter::kHead_IterStart);
+ while (freeNode) {
+ SkASSERT(fFreeList.isInList(freeNode));
+ Block* block = freeNode->fBlock;
+ SkASSERT(block->fNodesInUse > 0 && block->fNodesInUse < fAllocCnt);
+
+ int activeCnt = 0;
+ int freeCnt = 0;
+ for (int i = 0; i < fAllocCnt; ++i) {
+ bool free = fFreeList.isInList(block->fNodes + i);
+ bool active = fList.isInList(block->fNodes + i);
+ SkASSERT(free != active);
+ activeCnt += active;
+ freeCnt += free;
+ }
+ SkASSERT(activeCnt == block->fNodesInUse);
+ freeNode = iter.next();
+ }
+
+ int count = 0;
+ Node* activeNode = iter.init(fList, Iter::kHead_IterStart);
+ while (activeNode) {
+ ++count;
+ SkASSERT(fList.isInList(activeNode));
+ Block* block = activeNode->fBlock;
+ SkASSERT(block->fNodesInUse > 0 && block->fNodesInUse <= fAllocCnt);
+
+ int activeCnt = 0;
+ int freeCnt = 0;
+ for (int i = 0; i < fAllocCnt; ++i) {
+ bool free = fFreeList.isInList(block->fNodes + i);
+ bool active = fList.isInList(block->fNodes + i);
+ SkASSERT(free != active);
+ activeCnt += active;
+ freeCnt += free;
+ }
+ SkASSERT(activeCnt == block->fNodesInUse);
+ activeNode = iter.next();
+ }
+ SkASSERT(count == fCount);
+#endif
+ }
+
+ // Support in-place initializing of objects inserted into the list via operator new.
+ friend void* operator new<T>(size_t,
+ SkTLList* list,
+ Placement placement,
+ const Iter& location);
+
+
+ // Helpers that insert the node and returns a pointer to where the new object should be init'ed.
+ void* internalAddBefore(Iter location) {
+ this->validate();
+ Node* node = this->createNode();
+ fList.addBefore(node, location.getNode());
+ this->validate();
+ return node->fObj;
+ }
+
+ void* internalAddAfter(Iter location) {
+ this->validate();
+ Node* node = this->createNode();
+ fList.addAfter(node, location.getNode());
+ this->validate();
+ return node->fObj;
+ }
+
+ NodeList fList;
+ NodeList fFreeList;
+ int fCount;
+ int fAllocCnt;
+
+};
+
+// Use the below macros rather than calling this directly
+template <typename T>
+void *operator new(size_t, SkTLList<T>* list,
+ typename SkTLList<T>::Placement placement,
+ const typename SkTLList<T>::Iter& location) {
+ SkASSERT(NULL != list);
+ if (SkTLList<T>::kBefore_Placement == placement) {
+ return list->internalAddBefore(location);
+ } else {
+ return list->internalAddAfter(location);
+ }
+}
+
+// Skia doesn't use C++ exceptions but it may be compiled with them enabled. Having an op delete
+// to match the op new silences warnings about missing op delete when a constructor throws an
+// exception.
+template <typename T>
+void operator delete(void*,
+ SkTLList<T>*,
+ typename SkTLList<T>::Placement,
+ const typename SkTLList<T>::Iter&) {
+ SK_CRASH();
+}
+
+#define SkNEW_INSERT_IN_LLIST_BEFORE(list, location, type_name, args) \
+ (new ((list), SkTLList< type_name >::kBefore_Placement, (location)) type_name args)
+
+#define SkNEW_INSERT_IN_LLIST_AFTER(list, location, type_name, args) \
+ (new ((list), SkTLList< type_name >::kAfter_Placement, (location)) type_name args)
+
+#define SkNEW_INSERT_AT_LLIST_HEAD(list, type_name, args) \
+ SkNEW_INSERT_IN_LLIST_BEFORE((list), (list)->headIter(), type_name, args)
+
+#define SkNEW_INSERT_AT_LLIST_TAIL(list, type_name, args) \
+ SkNEW_INSERT_IN_LLIST_AFTER((list), (list)->tailIter(), type_name, args)
+
+#endif
diff --git a/src/core/SkTileGrid.cpp b/src/core/SkTileGrid.cpp
new file mode 100644
index 0000000000..2b885c9f1f
--- /dev/null
+++ b/src/core/SkTileGrid.cpp
@@ -0,0 +1,105 @@
+
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkTileGrid.h"
+
+SkTileGrid::SkTileGrid(int tileWidth, int tileHeight, int xTileCount, int yTileCount, SkTileGridNextDatumFunctionPtr nextDatumFunction)
+{
+ fTileWidth = tileWidth;
+ fTileHeight = tileHeight;
+ fXTileCount = xTileCount;
+ fYTileCount = yTileCount;
+ fTileCount = fXTileCount * fYTileCount;
+ fInsertionCount = 0;
+ fGridBounds = SkIRect::MakeXYWH(0, 0, fTileWidth * fXTileCount, fTileHeight * fYTileCount);
+ fNextDatumFunction = nextDatumFunction;
+ fTileData = SkNEW_ARRAY(SkTDArray<void *>, fTileCount);
+}
+
+SkTileGrid::~SkTileGrid() {
+ SkDELETE_ARRAY(fTileData);
+}
+
+SkTDArray<void *>& SkTileGrid::tile(int x, int y) {
+ return fTileData[y * fXTileCount + x];
+}
+
+void SkTileGrid::insert(void* data, const SkIRect& bounds, bool) {
+ SkASSERT(!bounds.isEmpty());
+ SkIRect dilatedBounds = bounds;
+ dilatedBounds.outset(1,1); // Consideration for filtering and AA
+
+ if (!SkIRect::Intersects(dilatedBounds, fGridBounds)) {
+ return;
+ }
+
+ int minTileX = SkMax32(SkMin32(dilatedBounds.left() / fTileWidth, fXTileCount - 1), 0);
+ int maxTileX = SkMax32(SkMin32(dilatedBounds.right() / fTileWidth, fXTileCount - 1), 0);
+ int minTileY = SkMax32(SkMin32(dilatedBounds.top() / fTileHeight, fYTileCount -1), 0);
+ int maxTileY = SkMax32(SkMin32(dilatedBounds.bottom() / fTileHeight, fYTileCount -1), 0);
+
+ for (int x = minTileX; x <= maxTileX; x++) {
+ for (int y = minTileY; y <= maxTileY; y++) {
+ this->tile(x, y).push(data);
+ }
+ }
+ fInsertionCount++;
+}
+
+void SkTileGrid::search(const SkIRect& query, SkTDArray<void*>* results) {
+ // The +1/-1 is to compensate for the outset in applied SkCanvas::getClipBounds
+ int tileStartX = (query.left() + 1) / fTileWidth;
+ int tileEndX = (query.right() + fTileWidth - 1) / fTileWidth;
+ int tileStartY = (query.top() + 1) / fTileHeight;
+ int tileEndY = (query.bottom() + fTileHeight - 1) / fTileHeight;
+ if (tileStartX >= fXTileCount || tileStartY >= fYTileCount || tileEndX <= 0 || tileEndY <= 0) {
+ return; // query does not intersect the grid
+ }
+ // clamp to grid
+ if (tileStartX < 0) tileStartX = 0;
+ if (tileStartY < 0) tileStartY = 0;
+ if (tileEndX > fXTileCount) tileEndX = fXTileCount;
+ if (tileEndY > fYTileCount) tileEndY = fYTileCount;
+
+ int queryTileCount = (tileEndX - tileStartX) * (tileEndY - tileStartY);
+ if (queryTileCount == 1) {
+ *results = this->tile(tileStartX, tileStartY);
+ } else {
+ results->reset();
+ SkTDArray<int> curPositions;
+ curPositions.setCount(queryTileCount);
+ // Note: Reserving space for 1024 tile pointers on the stack. If the
+ // malloc becomes a bottleneck, we may consider increasing that number.
+ // Typical large web page, say 2k x 16k, would require 512 tiles of
+ // size 256 x 256 pixels.
+ SkAutoSTArray<1024, SkTDArray<void *>*> storage(queryTileCount);
+ SkTDArray<void *>** tileRange = storage.get();
+ int tile = 0;
+ for (int x = tileStartX; x < tileEndX; ++x) {
+ for (int y = tileStartY; y < tileEndY; ++y) {
+ tileRange[tile] = &this->tile(x, y);
+ curPositions[tile] = tileRange[tile]->count() ? 0 : kTileFinished;
+ ++tile;
+ }
+ }
+ void *nextElement;
+ while(NULL != (nextElement = fNextDatumFunction(tileRange, curPositions))) {
+ results->push(nextElement);
+ }
+ }
+}
+
+void SkTileGrid::clear() {
+ for (int i = 0; i < fTileCount; i++) {
+ fTileData[i].reset();
+ }
+}
+
+int SkTileGrid::getCount() const {
+ return fInsertionCount;
+}
diff --git a/src/core/SkTileGrid.h b/src/core/SkTileGrid.h
new file mode 100644
index 0000000000..8ae3f2c1cc
--- /dev/null
+++ b/src/core/SkTileGrid.h
@@ -0,0 +1,120 @@
+
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkTileGrid_DEFINED
+#define SkTileGrid_DEFINED
+
+#include "SkBBoxHierarchy.h"
+#include "SkPictureStateTree.h"
+
+/**
+ * Subclass of SkBBoxHierarchy that stores elements in buckets that correspond
+ * to tile regions, disposed in a regular grid. This is useful when the tile
+ * structure that will be use in search() calls is known prior to insertion.
+ * Calls to search will return in constant time.
+ *
+ * Note: Current implementation of search() only supports looking-up regions
+ * that are an exact match to a single tile. Implementation could be augmented
+ * to support arbitrary rectangles, but performance would be sub-optimal.
+ */
+class SkTileGrid : public SkBBoxHierarchy {
+public:
+ typedef void* (*SkTileGridNextDatumFunctionPtr)(SkTDArray<void*>** tileData, SkTDArray<int>& tileIndices);
+
+ SkTileGrid(int tileWidth, int tileHeight, int xTileCount, int yTileCount,
+ SkTileGridNextDatumFunctionPtr nextDatumFunction);
+
+ virtual ~SkTileGrid();
+
+ /**
+ * Insert a data pointer and corresponding bounding box
+ * @param data The data pointer, may be NULL
+ * @param bounds The bounding box, should not be empty
+ * @param defer Ignored, TileArray does not defer insertions
+ */
+ virtual void insert(void* data, const SkIRect& bounds, bool) SK_OVERRIDE;
+
+ virtual void flushDeferredInserts() SK_OVERRIDE {};
+
+ /**
+ * Populate 'results' with data pointers corresponding to bounding boxes that intersect 'query'
+ * The query argument is expected to be an exact match to a tile of the grid
+ */
+ virtual void search(const SkIRect& query, SkTDArray<void*>* results) SK_OVERRIDE;
+
+ virtual void clear() SK_OVERRIDE;
+
+ /**
+ * Gets the number of insertions
+ */
+ virtual int getCount() const SK_OVERRIDE;
+
+ // Used by search() and in SkTileGridHelper implementations
+ enum {
+ kTileFinished = -1,
+ };
+private:
+ SkTDArray<void*>& tile(int x, int y);
+
+ int fTileWidth, fTileHeight, fXTileCount, fYTileCount, fTileCount;
+ SkTDArray<void*>* fTileData;
+ int fInsertionCount;
+ SkIRect fGridBounds;
+ SkTileGridNextDatumFunctionPtr fNextDatumFunction;
+
+ friend class TileGridTest;
+ typedef SkBBoxHierarchy INHERITED;
+};
+
+/**
+ * Generic implementation for SkTileGridNextDatumFunctionPtr. user code may instantiate
+ * this template to get a valid SkTileGridNextDatumFunction implementation
+ *
+ * Returns the next element of tileData[i][tileIndices[i]] for all i and advances
+ * tileIndices[] past them. The order in which data are returned by successive
+ * calls to this method must reflect the order in which the were originally
+ * recorded into the tile grid.
+ *
+ * \param tileData array of pointers to arrays of tile data
+ * \param tileIndices per-tile data indices, indices are incremented for tiles that contain
+ * the next datum.
+ * \tparam T a type to which it is safe to cast a datum and that has an operator <
+ * such that 'a < b' is true if 'a' was inserted into the tile grid before 'b'.
+ */
+template <typename T>
+void* SkTileGridNextDatum(SkTDArray<void*>** tileData, SkTDArray<int>& tileIndices) {
+ bool haveVal = false;
+ T* minVal;
+ int tileCount = tileIndices.count();
+ // Find the next Datum
+ for (int tile = 0; tile < tileCount; ++tile) {
+ int pos = tileIndices[tile];
+ if (pos != SkTileGrid::kTileFinished) {
+ T* candidate = (T*)(*tileData[tile])[pos];
+ if (!haveVal || (*candidate) < (*minVal)) {
+ minVal = candidate;
+ haveVal = true;
+ }
+ }
+ }
+ // Increment indices past the next datum
+ if (haveVal) {
+ for (int tile = 0; tile < tileCount; ++tile) {
+ int pos = tileIndices[tile];
+ if (pos != SkTileGrid::kTileFinished && (*tileData[tile])[pos] == minVal) {
+ if (++(tileIndices[tile]) >= tileData[tile]->count()) {
+ tileIndices[tile] = SkTileGrid::kTileFinished;
+ }
+ }
+ }
+ return minVal;
+ }
+ return NULL;
+}
+
+#endif
diff --git a/src/core/SkTileGridPicture.cpp b/src/core/SkTileGridPicture.cpp
new file mode 100644
index 0000000000..212e3b6243
--- /dev/null
+++ b/src/core/SkTileGridPicture.cpp
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkTileGridPicture.h"
+
+#include "SkPictureStateTree.h"
+#include "SkTileGrid.h"
+
+
+SkTileGridPicture::SkTileGridPicture(int tileWidth, int tileHeight, int width, int height) {
+ fTileWidth = tileWidth;
+ fTileHeight = tileHeight;
+ fXTileCount = (width + tileWidth - 1) / tileWidth;
+ fYTileCount = (height + tileHeight - 1) / tileHeight;
+}
+
+SkBBoxHierarchy* SkTileGridPicture::createBBoxHierarchy() const {
+ return SkNEW_ARGS(SkTileGrid, (fTileWidth, fTileHeight, fXTileCount, fYTileCount,
+ SkTileGridNextDatum<SkPictureStateTree::Draw>));
+}
diff --git a/src/core/SkWriter32.cpp b/src/core/SkWriter32.cpp
index 6a8492731b..4bb83b3e37 100644
--- a/src/core/SkWriter32.cpp
+++ b/src/core/SkWriter32.cpp
@@ -66,6 +66,7 @@ SkWriter32::SkWriter32(size_t minSize, void* storage, size_t storageSize) {
fSize = 0;
fSingleBlock = NULL;
fSingleBlockSize = 0;
+ fWrittenBeforeLastBlock = 0;
storageSize &= ~3; // trunc down to multiple of 4
if (storageSize >= MIN_BLOCKSIZE) {
@@ -96,6 +97,7 @@ void SkWriter32::reset() {
}
fSize = 0;
+ fWrittenBeforeLastBlock = 0;
fSingleBlock = NULL;
if (fHeadIsExternalStorage) {
SkASSERT(fHead);
@@ -128,7 +130,11 @@ uint32_t* SkWriter32::reserve(size_t size) {
if (NULL == block) {
SkASSERT(NULL == fHead);
fHead = fTail = block = Block::Create(SkMax32(size, fMinSize));
+ SkASSERT(0 == fWrittenBeforeLastBlock);
} else if (block->available() < size) {
+ SkASSERT(fSize > 0);
+ fWrittenBeforeLastBlock = fSize;
+
fTail = Block::Create(SkMax32(size, fMinSize));
block->fNext = fTail;
block = fTail;
@@ -149,6 +155,11 @@ uint32_t* SkWriter32::peek32(size_t offset) {
return (uint32_t*)(fSingleBlock + offset);
}
+ // try the fast case, where offset is within fTail
+ if (offset >= fWrittenBeforeLastBlock) {
+ return fTail->peek32(offset - fWrittenBeforeLastBlock);
+ }
+
Block* block = fHead;
SkASSERT(NULL != block);
@@ -179,29 +190,39 @@ void SkWriter32::rewindToOffset(size_t offset) {
return;
}
- // Similar to peek32, except that we free up any following blocks
- Block* block = fHead;
- SkASSERT(NULL != block);
- while (offset >= block->fAllocatedSoFar) {
- offset -= block->fAllocatedSoFar;
- block = block->fNext;
+ // Try the fast case, where offset is within fTail
+ if (offset >= fWrittenBeforeLastBlock) {
+ fTail->fAllocatedSoFar = offset - fWrittenBeforeLastBlock;
+ } else {
+ // Similar to peek32, except that we free up any following blocks.
+ // We have to re-compute fWrittenBeforeLastBlock as well.
+
+ size_t globalOffset = offset;
+ Block* block = fHead;
SkASSERT(NULL != block);
- }
+ while (offset >= block->fAllocatedSoFar) {
+ offset -= block->fAllocatedSoFar;
+ block = block->fNext;
+ SkASSERT(NULL != block);
+ }
- // update the size on the "last" block
- block->fAllocatedSoFar = offset;
- // end our list
- fTail = block;
- Block* next = block->fNext;
- block->fNext = NULL;
- // free up any trailing blocks
- block = next;
- while (block) {
+ // this has to be recomputed, since we may free up fTail
+ fWrittenBeforeLastBlock = globalOffset - offset;
+
+ // update the size on the "last" block
+ block->fAllocatedSoFar = offset;
+ // end our list
+ fTail = block;
Block* next = block->fNext;
- sk_free(block);
+ block->fNext = NULL;
+ // free up any trailing blocks
block = next;
+ while (block) {
+ Block* next = block->fNext;
+ sk_free(block);
+ block = next;
+ }
}
-
SkDEBUGCODE(this->validate();)
}
@@ -310,6 +331,10 @@ void SkWriter32::validate() const {
SkASSERT(SkIsAlign4(block->fSizeOfBlock));
SkASSERT(SkIsAlign4(block->fAllocatedSoFar));
SkASSERT(block->fAllocatedSoFar <= block->fSizeOfBlock);
+ if (NULL == block->fNext) {
+ SkASSERT(fTail == block);
+ SkASSERT(fWrittenBeforeLastBlock == accum);
+ }
accum += block->fAllocatedSoFar;
SkASSERT(accum <= fSize);
block = block->fNext;
diff --git a/src/core/SkXfermode.cpp b/src/core/SkXfermode.cpp
index aeed9cc26e..7a25c6e18b 100644
--- a/src/core/SkXfermode.cpp
+++ b/src/core/SkXfermode.cpp
@@ -450,22 +450,22 @@ static const ProcCoeff gProcCoeffs[] = {
///////////////////////////////////////////////////////////////////////////////
-bool SkXfermode::asCoeff(Coeff* src, Coeff* dst) {
+bool SkXfermode::asCoeff(Coeff* src, Coeff* dst) const {
return false;
}
-bool SkXfermode::asMode(Mode* mode) {
+bool SkXfermode::asMode(Mode* mode) const {
return false;
}
-SkPMColor SkXfermode::xferColor(SkPMColor src, SkPMColor dst) {
+SkPMColor SkXfermode::xferColor(SkPMColor src, SkPMColor dst) const{
// no-op. subclasses should override this
return dst;
}
void SkXfermode::xfer32(SkPMColor* SK_RESTRICT dst,
const SkPMColor* SK_RESTRICT src, int count,
- const SkAlpha* SK_RESTRICT aa) {
+ const SkAlpha* SK_RESTRICT aa) const {
SkASSERT(dst && src && count >= 0);
if (NULL == aa) {
@@ -489,7 +489,7 @@ void SkXfermode::xfer32(SkPMColor* SK_RESTRICT dst,
void SkXfermode::xfer16(uint16_t* dst,
const SkPMColor* SK_RESTRICT src, int count,
- const SkAlpha* SK_RESTRICT aa) {
+ const SkAlpha* SK_RESTRICT aa) const {
SkASSERT(dst && src && count >= 0);
if (NULL == aa) {
@@ -514,8 +514,7 @@ void SkXfermode::xfer16(uint16_t* dst,
void SkXfermode::xfer4444(SkPMColor16* SK_RESTRICT dst,
const SkPMColor* SK_RESTRICT src, int count,
- const SkAlpha* SK_RESTRICT aa)
-{
+ const SkAlpha* SK_RESTRICT aa) const {
SkASSERT(dst && src && count >= 0);
if (NULL == aa) {
@@ -540,8 +539,7 @@ void SkXfermode::xfer4444(SkPMColor16* SK_RESTRICT dst,
void SkXfermode::xferA8(SkAlpha* SK_RESTRICT dst,
const SkPMColor src[], int count,
- const SkAlpha* SK_RESTRICT aa)
-{
+ const SkAlpha* SK_RESTRICT aa) const {
SkASSERT(dst && src && count >= 0);
if (NULL == aa) {
@@ -569,7 +567,7 @@ void SkXfermode::xferA8(SkAlpha* SK_RESTRICT dst,
void SkProcXfermode::xfer32(SkPMColor* SK_RESTRICT dst,
const SkPMColor* SK_RESTRICT src, int count,
- const SkAlpha* SK_RESTRICT aa) {
+ const SkAlpha* SK_RESTRICT aa) const {
SkASSERT(dst && src && count >= 0);
SkXfermodeProc proc = fProc;
@@ -597,7 +595,7 @@ void SkProcXfermode::xfer32(SkPMColor* SK_RESTRICT dst,
void SkProcXfermode::xfer16(uint16_t* SK_RESTRICT dst,
const SkPMColor* SK_RESTRICT src, int count,
- const SkAlpha* SK_RESTRICT aa) {
+ const SkAlpha* SK_RESTRICT aa) const {
SkASSERT(dst && src && count >= 0);
SkXfermodeProc proc = fProc;
@@ -626,7 +624,7 @@ void SkProcXfermode::xfer16(uint16_t* SK_RESTRICT dst,
void SkProcXfermode::xfer4444(SkPMColor16* SK_RESTRICT dst,
const SkPMColor* SK_RESTRICT src, int count,
- const SkAlpha* SK_RESTRICT aa) {
+ const SkAlpha* SK_RESTRICT aa) const {
SkASSERT(dst && src && count >= 0);
SkXfermodeProc proc = fProc;
@@ -655,7 +653,7 @@ void SkProcXfermode::xfer4444(SkPMColor16* SK_RESTRICT dst,
void SkProcXfermode::xferA8(SkAlpha* SK_RESTRICT dst,
const SkPMColor* SK_RESTRICT src, int count,
- const SkAlpha* SK_RESTRICT aa) {
+ const SkAlpha* SK_RESTRICT aa) const {
SkASSERT(dst && src && count >= 0);
SkXfermodeProc proc = fProc;
@@ -711,14 +709,14 @@ public:
fDstCoeff = rec.fDC;
}
- virtual bool asMode(Mode* mode) {
+ virtual bool asMode(Mode* mode) const SK_OVERRIDE {
if (mode) {
*mode = fMode;
}
return true;
}
- virtual bool asCoeff(Coeff* sc, Coeff* dc) {
+ virtual bool asCoeff(Coeff* sc, Coeff* dc) const SK_OVERRIDE {
if (CANNOT_USE_COEFF == fSrcCoeff) {
return false;
}
@@ -765,8 +763,8 @@ class SkClearXfermode : public SkProcCoeffXfermode {
public:
SkClearXfermode(const ProcCoeff& rec) : SkProcCoeffXfermode(rec, kClear_Mode) {}
- virtual void xfer32(SkPMColor*, const SkPMColor*, int, const SkAlpha*) SK_OVERRIDE;
- virtual void xferA8(SkAlpha*, const SkPMColor*, int, const SkAlpha*) SK_OVERRIDE;
+ virtual void xfer32(SkPMColor*, const SkPMColor*, int, const SkAlpha*) const SK_OVERRIDE;
+ virtual void xferA8(SkAlpha*, const SkPMColor*, int, const SkAlpha*) const SK_OVERRIDE;
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkClearXfermode)
@@ -778,7 +776,7 @@ private:
void SkClearXfermode::xfer32(SkPMColor* SK_RESTRICT dst,
const SkPMColor* SK_RESTRICT, int count,
- const SkAlpha* SK_RESTRICT aa) {
+ const SkAlpha* SK_RESTRICT aa) const {
SkASSERT(dst && count >= 0);
if (NULL == aa) {
@@ -796,7 +794,7 @@ void SkClearXfermode::xfer32(SkPMColor* SK_RESTRICT dst,
}
void SkClearXfermode::xferA8(SkAlpha* SK_RESTRICT dst,
const SkPMColor* SK_RESTRICT, int count,
- const SkAlpha* SK_RESTRICT aa) {
+ const SkAlpha* SK_RESTRICT aa) const {
SkASSERT(dst && count >= 0);
if (NULL == aa) {
@@ -819,8 +817,8 @@ class SkSrcXfermode : public SkProcCoeffXfermode {
public:
SkSrcXfermode(const ProcCoeff& rec) : SkProcCoeffXfermode(rec, kSrc_Mode) {}
- virtual void xfer32(SkPMColor*, const SkPMColor*, int, const SkAlpha*) SK_OVERRIDE;
- virtual void xferA8(SkAlpha*, const SkPMColor*, int, const SkAlpha*) SK_OVERRIDE;
+ virtual void xfer32(SkPMColor*, const SkPMColor*, int, const SkAlpha*) const SK_OVERRIDE;
+ virtual void xferA8(SkAlpha*, const SkPMColor*, int, const SkAlpha*) const SK_OVERRIDE;
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkSrcXfermode)
@@ -832,7 +830,7 @@ private:
void SkSrcXfermode::xfer32(SkPMColor* SK_RESTRICT dst,
const SkPMColor* SK_RESTRICT src, int count,
- const SkAlpha* SK_RESTRICT aa) {
+ const SkAlpha* SK_RESTRICT aa) const {
SkASSERT(dst && src && count >= 0);
if (NULL == aa) {
@@ -851,7 +849,7 @@ void SkSrcXfermode::xfer32(SkPMColor* SK_RESTRICT dst,
void SkSrcXfermode::xferA8(SkAlpha* SK_RESTRICT dst,
const SkPMColor* SK_RESTRICT src, int count,
- const SkAlpha* SK_RESTRICT aa) {
+ const SkAlpha* SK_RESTRICT aa) const {
SkASSERT(dst && src && count >= 0);
if (NULL == aa) {
@@ -873,13 +871,13 @@ void SkSrcXfermode::xferA8(SkAlpha* SK_RESTRICT dst,
}
}
-////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
class SkDstInXfermode : public SkProcCoeffXfermode {
public:
SkDstInXfermode(const ProcCoeff& rec) : SkProcCoeffXfermode(rec, kDstIn_Mode) {}
- virtual void xfer32(SkPMColor*, const SkPMColor*, int, const SkAlpha*) SK_OVERRIDE;
+ virtual void xfer32(SkPMColor*, const SkPMColor*, int, const SkAlpha*) const SK_OVERRIDE;
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkDstInXfermode)
@@ -891,7 +889,7 @@ private:
void SkDstInXfermode::xfer32(SkPMColor* SK_RESTRICT dst,
const SkPMColor* SK_RESTRICT src, int count,
- const SkAlpha* SK_RESTRICT aa) {
+ const SkAlpha* SK_RESTRICT aa) const {
SkASSERT(dst && src);
if (count <= 0) {
@@ -909,13 +907,13 @@ void SkDstInXfermode::xfer32(SkPMColor* SK_RESTRICT dst,
} while (--count != 0);
}
-/////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
class SkDstOutXfermode : public SkProcCoeffXfermode {
public:
SkDstOutXfermode(const ProcCoeff& rec) : SkProcCoeffXfermode(rec, kDstOut_Mode) {}
- virtual void xfer32(SkPMColor*, const SkPMColor*, int, const SkAlpha*) SK_OVERRIDE;
+ virtual void xfer32(SkPMColor*, const SkPMColor*, int, const SkAlpha*) const SK_OVERRIDE;
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkDstOutXfermode)
@@ -928,7 +926,7 @@ private:
void SkDstOutXfermode::xfer32(SkPMColor* SK_RESTRICT dst,
const SkPMColor* SK_RESTRICT src, int count,
- const SkAlpha* SK_RESTRICT aa) {
+ const SkAlpha* SK_RESTRICT aa) const {
SkASSERT(dst && src);
if (count <= 0) {
@@ -1002,7 +1000,7 @@ bool SkXfermode::ModeAsCoeff(Mode mode, Coeff* src, Coeff* dst) {
return true;
}
-bool SkXfermode::AsMode(SkXfermode* xfer, Mode* mode) {
+bool SkXfermode::AsMode(const SkXfermode* xfer, Mode* mode) {
if (NULL == xfer) {
if (mode) {
*mode = kSrcOver_Mode;
@@ -1012,14 +1010,14 @@ bool SkXfermode::AsMode(SkXfermode* xfer, Mode* mode) {
return xfer->asMode(mode);
}
-bool SkXfermode::AsCoeff(SkXfermode* xfer, Coeff* src, Coeff* dst) {
+bool SkXfermode::AsCoeff(const SkXfermode* xfer, Coeff* src, Coeff* dst) {
if (NULL == xfer) {
return ModeAsCoeff(kSrcOver_Mode, src, dst);
}
return xfer->asCoeff(src, dst);
}
-bool SkXfermode::IsMode(SkXfermode* xfer, Mode mode) {
+bool SkXfermode::IsMode(const SkXfermode* xfer, Mode mode) {
// if xfer==null then the mode is srcover
Mode m = kSrcOver_Mode;
if (xfer && !xfer->asMode(&m)) {
diff --git a/src/device/xps/SkXPSDevice.cpp b/src/device/xps/SkXPSDevice.cpp
new file mode 100644
index 0000000000..e1d5eedeaa
--- /dev/null
+++ b/src/device/xps/SkXPSDevice.cpp
@@ -0,0 +1,2425 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef UNICODE
+#define UNICODE
+#endif
+#ifndef _UNICODE
+#define _UNICODE
+#endif
+#include "SkTypes.h"
+#include <ObjBase.h>
+#include <XpsObjectModel.h>
+#include <T2EmbApi.h>
+#include <FontSub.h>
+
+#include "SkColor.h"
+#include "SkConstexprMath.h"
+#include "SkData.h"
+#include "SkDraw.h"
+#include "SkDrawProcs.h"
+#include "SkFontHost.h"
+#include "SkGlyphCache.h"
+#include "SkHRESULT.h"
+#include "SkImageEncoder.h"
+#include "SkIStream.h"
+#include "SkMaskFilter.h"
+#include "SkPaint.h"
+#include "SkPoint.h"
+#include "SkRasterizer.h"
+#include "SkShader.h"
+#include "SkSize.h"
+#include "SkStream.h"
+#include "SkTDArray.h"
+#include "SkTLazy.h"
+#include "SkTScopedComPtr.h"
+#include "SkUtils.h"
+#include "SkXPSDevice.h"
+
+//Windows defines a FLOAT type,
+//make it clear when converting a scalar that this is what is wanted.
+#define SkScalarToFLOAT(n) SkScalarToFloat(n)
+
+//Dummy representation of a GUID from create_id.
+#define L_GUID_ID L"XXXXXXXXsXXXXsXXXXsXXXXsXXXXXXXXXXXX"
+//Length of GUID representation from create_id, including NULL terminator.
+#define GUID_ID_LEN SK_ARRAY_COUNT(L_GUID_ID)
+
+/**
+ Formats a GUID and places it into buffer.
+ buffer should have space for at least GUID_ID_LEN wide characters.
+ The string will always be wchar null terminated.
+ XXXXXXXXsXXXXsXXXXsXXXXsXXXXXXXXXXXX0
+ @return -1 if there was an error, > 0 if success.
+ */
+static int format_guid(const GUID& guid,
+ wchar_t* buffer, size_t bufferSize,
+ wchar_t sep = '-') {
+ SkASSERT(bufferSize >= GUID_ID_LEN);
+ return swprintf_s(buffer,
+ bufferSize,
+ L"%08lX%c%04X%c%04X%c%02X%02X%c%02X%02X%02X%02X%02X%02X",
+ guid.Data1,
+ sep,
+ guid.Data2,
+ sep,
+ guid.Data3,
+ sep,
+ guid.Data4[0],
+ guid.Data4[1],
+ sep,
+ guid.Data4[2],
+ guid.Data4[3],
+ guid.Data4[4],
+ guid.Data4[5],
+ guid.Data4[6],
+ guid.Data4[7]);
+}
+/**
+ Creates a GUID based id and places it into buffer.
+ buffer should have space for at least GUID_ID_LEN wide characters.
+ The string will always be wchar null terminated.
+ XXXXXXXXsXXXXsXXXXsXXXXsXXXXXXXXXXXX0
+ The string may begin with a digit,
+ and so may not be suitable as a bare resource key.
+ */
+static HRESULT create_id(wchar_t* buffer, size_t bufferSize,
+ wchar_t sep = '-') {
+ GUID guid = {};
+ HRM(CoCreateGuid(&guid), "Could not create GUID for id.");
+
+ if (format_guid(guid, buffer, bufferSize, sep) == -1) {
+ HRM(E_UNEXPECTED, "Could not format GUID into id.");
+ }
+
+ return S_OK;
+}
+
+static SkBitmap make_fake_bitmap(int width, int height) {
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kNo_Config, width, height);
+ return bitmap;
+}
+
+SkXPSDevice::SkXPSDevice()
+ : SkDevice(make_fake_bitmap(10000, 10000))
+ , fCurrentPage(0) {
+}
+
+SkXPSDevice::~SkXPSDevice() {
+}
+
+SkXPSDevice::TypefaceUse::TypefaceUse()
+ : typefaceId(0xffffffff)
+ , fontData(NULL)
+ , xpsFont(NULL)
+ , glyphsUsed(NULL) {
+}
+
+SkXPSDevice::TypefaceUse::~TypefaceUse() {
+ //xpsFont owns fontData ref
+ this->xpsFont->Release();
+ delete this->glyphsUsed;
+}
+
+bool SkXPSDevice::beginPortfolio(SkWStream* outputStream) {
+ if (!this->fAutoCo.succeeded()) return false;
+
+ //Create XPS Factory.
+ HRBM(CoCreateInstance(
+ CLSID_XpsOMObjectFactory,
+ NULL,
+ CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(&this->fXpsFactory)),
+ "Could not create XPS factory.");
+
+ HRBM(SkWIStream::CreateFromSkWStream(outputStream, &this->fOutputStream),
+ "Could not convert SkStream to IStream.");
+
+ return true;
+}
+
+bool SkXPSDevice::beginSheet(
+ const SkVector& unitsPerMeter,
+ const SkVector& pixelsPerMeter,
+ const SkSize& trimSize,
+ const SkRect* mediaBox,
+ const SkRect* bleedBox,
+ const SkRect* artBox,
+ const SkRect* cropBox) {
+ ++this->fCurrentPage;
+
+ //For simplicity, just write everything out in geometry units,
+ //then have a base canvas do the scale to physical units.
+ this->fCurrentCanvasSize = trimSize;
+ this->fCurrentUnitsPerMeter = unitsPerMeter;
+ this->fCurrentPixelsPerMeter = pixelsPerMeter;
+
+ this->fCurrentXpsCanvas.reset();
+ HRBM(this->fXpsFactory->CreateCanvas(&this->fCurrentXpsCanvas),
+ "Could not create base canvas.");
+
+ return true;
+}
+
+HRESULT SkXPSDevice::createXpsThumbnail(IXpsOMPage* page,
+ const unsigned int pageNum,
+ IXpsOMImageResource** image) {
+ SkTScopedComPtr<IXpsOMThumbnailGenerator> thumbnailGenerator;
+ HRM(CoCreateInstance(
+ CLSID_XpsOMThumbnailGenerator,
+ NULL,
+ CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(&thumbnailGenerator)),
+ "Could not create thumbnail generator.");
+
+ SkTScopedComPtr<IOpcPartUri> partUri;
+ static const size_t size = SK_MAX(
+ SK_ARRAY_COUNT(L"/Documents/1/Metadata/.png") + SK_DIGITS_IN(pageNum),
+ SK_ARRAY_COUNT(L"/Metadata/" L_GUID_ID L".png")
+ );
+ wchar_t buffer[size];
+ if (pageNum > 0) {
+ swprintf_s(buffer, size, L"/Documents/1/Metadata/%u.png", pageNum);
+ } else {
+ wchar_t id[GUID_ID_LEN];
+ HR(create_id(id, GUID_ID_LEN));
+ swprintf_s(buffer, size, L"/Metadata/%s.png", id);
+ }
+ HRM(this->fXpsFactory->CreatePartUri(buffer, &partUri),
+ "Could not create thumbnail part uri.");
+
+ HRM(thumbnailGenerator->GenerateThumbnail(page,
+ XPS_IMAGE_TYPE_PNG,
+ XPS_THUMBNAIL_SIZE_LARGE,
+ partUri.get(),
+ image),
+ "Could not generate thumbnail.");
+
+ return S_OK;
+}
+
+HRESULT SkXPSDevice::createXpsPage(const XPS_SIZE& pageSize,
+ IXpsOMPage** page) {
+ static const size_t size = SK_ARRAY_COUNT(L"/Documents/1/Pages/.fpage")
+ + SK_DIGITS_IN(fCurrentPage);
+ wchar_t buffer[size];
+ swprintf_s(buffer, size, L"/Documents/1/Pages/%u.fpage",
+ this->fCurrentPage);
+ SkTScopedComPtr<IOpcPartUri> partUri;
+ HRM(this->fXpsFactory->CreatePartUri(buffer, &partUri),
+ "Could not create page part uri.");
+
+ //If the language is unknown, use "und" (XPS Spec 2.3.5.1).
+ HRM(this->fXpsFactory->CreatePage(&pageSize,
+ L"und",
+ partUri.get(),
+ page),
+ "Could not create page.");
+
+ return S_OK;
+}
+
+HRESULT SkXPSDevice::initXpsDocumentWriter(IXpsOMImageResource* image) {
+ //Create package writer.
+ {
+ SkTScopedComPtr<IOpcPartUri> partUri;
+ HRM(this->fXpsFactory->CreatePartUri(L"/FixedDocumentSequence.fdseq",
+ &partUri),
+ "Could not create document sequence part uri.");
+ HRM(this->fXpsFactory->CreatePackageWriterOnStream(
+ this->fOutputStream.get(),
+ TRUE,
+ XPS_INTERLEAVING_OFF, //XPS_INTERLEAVING_ON,
+ partUri.get(),
+ NULL,
+ image,
+ NULL,
+ NULL,
+ &this->fPackageWriter),
+ "Could not create package writer.");
+ }
+
+ //Begin the lone document.
+ {
+ SkTScopedComPtr<IOpcPartUri> partUri;
+ HRM(this->fXpsFactory->CreatePartUri(
+ L"/Documents/1/FixedDocument.fdoc",
+ &partUri),
+ "Could not create fixed document part uri.");
+ HRM(this->fPackageWriter->StartNewDocument(partUri.get(),
+ NULL,
+ NULL,
+ NULL,
+ NULL),
+ "Could not start document.");
+ }
+
+ return S_OK;
+}
+
+bool SkXPSDevice::endSheet() {
+ //XPS is fixed at 96dpi (XPS Spec 11.1).
+ static const float xpsDPI = 96.0f;
+ static const float inchesPerMeter = 10000.0f / 254.0f;
+ static const float targetUnitsPerMeter = xpsDPI * inchesPerMeter;
+ const float scaleX = targetUnitsPerMeter
+ / SkScalarToFLOAT(this->fCurrentUnitsPerMeter.fX);
+ const float scaleY = targetUnitsPerMeter
+ / SkScalarToFLOAT(this->fCurrentUnitsPerMeter.fY);
+
+ //Create the scale canvas.
+ SkTScopedComPtr<IXpsOMCanvas> scaleCanvas;
+ HRBM(this->fXpsFactory->CreateCanvas(&scaleCanvas),
+ "Could not create scale canvas.");
+ SkTScopedComPtr<IXpsOMVisualCollection> scaleCanvasVisuals;
+ HRBM(scaleCanvas->GetVisuals(&scaleCanvasVisuals),
+ "Could not get scale canvas visuals.");
+
+ SkTScopedComPtr<IXpsOMMatrixTransform> geomToPhys;
+ XPS_MATRIX rawGeomToPhys = { scaleX, 0, 0, scaleY, 0, 0, };
+ HRBM(this->fXpsFactory->CreateMatrixTransform(&rawGeomToPhys, &geomToPhys),
+ "Could not create geometry to physical transform.");
+ HRBM(scaleCanvas->SetTransformLocal(geomToPhys.get()),
+ "Could not set transform on scale canvas.");
+
+ //Add the content canvas to the scale canvas.
+ HRBM(scaleCanvasVisuals->Append(this->fCurrentXpsCanvas.get()),
+ "Could not add base canvas to scale canvas.");
+
+ //Create the page.
+ XPS_SIZE pageSize = {
+ SkScalarToFLOAT(this->fCurrentCanvasSize.width()) * scaleX,
+ SkScalarToFLOAT(this->fCurrentCanvasSize.height()) * scaleY,
+ };
+ SkTScopedComPtr<IXpsOMPage> page;
+ HRB(this->createXpsPage(pageSize, &page));
+
+ SkTScopedComPtr<IXpsOMVisualCollection> pageVisuals;
+ HRBM(page->GetVisuals(&pageVisuals), "Could not get page visuals.");
+
+ //Add the scale canvas to the page.
+ HRBM(pageVisuals->Append(scaleCanvas.get()),
+ "Could not add scale canvas to page.");
+
+ //Create the package writer if it hasn't been created yet.
+ if (NULL == this->fPackageWriter.get()) {
+ SkTScopedComPtr<IXpsOMImageResource> image;
+ //Ignore return, thumbnail is completely optional.
+ this->createXpsThumbnail(page.get(), 0, &image);
+
+ HRB(this->initXpsDocumentWriter(image.get()));
+ }
+
+ HRBM(this->fPackageWriter->AddPage(page.get(),
+ &pageSize,
+ NULL,
+ NULL,
+ NULL,
+ NULL),
+ "Could not write the page.");
+ this->fCurrentXpsCanvas.reset();
+
+ return true;
+}
+
+static HRESULT subset_typeface(SkXPSDevice::TypefaceUse* current) {
+ //CreateFontPackage wants unsigned short.
+ //Microsoft, Y U NO stdint.h?
+ SkTDArray<unsigned short> keepList;
+ current->glyphsUsed->exportTo(&keepList);
+
+ //The following are declared with the types required by CreateFontPackage.
+ unsigned char *puchFontPackageBuffer;
+ unsigned long pulFontPackageBufferSize;
+ unsigned long pulBytesWritten;
+ unsigned long result = CreateFontPackage(
+ (unsigned char *) current->fontData->getMemoryBase(),
+ (unsigned long) current->fontData->getLength(),
+ &puchFontPackageBuffer,
+ &pulFontPackageBufferSize,
+ &pulBytesWritten,
+ TTFCFP_FLAGS_SUBSET | TTFCFP_FLAGS_GLYPHLIST,// | TTFCFP_FLAGS_TTC,
+ 0,//TTC index
+ TTFCFP_SUBSET,
+ 0,
+ 0,
+ 0,
+ keepList.begin(),
+ keepList.count(),
+ sk_malloc_throw,
+ sk_realloc_throw,
+ sk_free,
+ NULL);
+ if (result != NO_ERROR) {
+ SkDEBUGF(("CreateFontPackage Error %lu", result));
+ return E_UNEXPECTED;
+ }
+
+ SkMemoryStream* newStream = new SkMemoryStream;
+ newStream->setMemoryOwned(puchFontPackageBuffer, pulBytesWritten);
+ SkTScopedComPtr<IStream> newIStream;
+ SkIStream::CreateFromSkStream(newStream, true, &newIStream);
+
+ XPS_FONT_EMBEDDING embedding;
+ HRM(current->xpsFont->GetEmbeddingOption(&embedding),
+ "Could not get embedding option from font.");
+
+ SkTScopedComPtr<IOpcPartUri> partUri;
+ HRM(current->xpsFont->GetPartName(&partUri),
+ "Could not get part uri from font.");
+
+ HRM(current->xpsFont->SetContent(
+ newIStream.get(),
+ embedding,
+ partUri.get()),
+ "Could not set new stream for subsetted font.");
+
+ return S_OK;
+}
+
+bool SkXPSDevice::endPortfolio() {
+ //Subset fonts
+ if (!this->fTypefaces.empty()) {
+ SkXPSDevice::TypefaceUse* current = &this->fTypefaces.front();
+ const TypefaceUse* last = &this->fTypefaces.back();
+ for (; current <= last; ++current) {
+ //Ignore return for now, if it didn't subset, let it be.
+ subset_typeface(current);
+ }
+ }
+
+ HRBM(this->fPackageWriter->Close(), "Could not close writer.");
+
+ return true;
+}
+
+static XPS_COLOR xps_color(const SkColor skColor) {
+ //XPS uses non-pre-multiplied alpha (XPS Spec 11.4).
+ XPS_COLOR xpsColor;
+ xpsColor.colorType = XPS_COLOR_TYPE_SRGB;
+ xpsColor.value.sRGB.alpha = SkColorGetA(skColor);
+ xpsColor.value.sRGB.red = SkColorGetR(skColor);
+ xpsColor.value.sRGB.green = SkColorGetG(skColor);
+ xpsColor.value.sRGB.blue = SkColorGetB(skColor);
+
+ return xpsColor;
+}
+
+static XPS_POINT xps_point(const SkPoint& point) {
+ XPS_POINT xpsPoint = {
+ SkScalarToFLOAT(point.fX),
+ SkScalarToFLOAT(point.fY),
+ };
+ return xpsPoint;
+}
+
+static XPS_POINT xps_point(const SkPoint& point, const SkMatrix& matrix) {
+ SkPoint skTransformedPoint;
+ matrix.mapXY(point.fX, point.fY, &skTransformedPoint);
+ return xps_point(skTransformedPoint);
+}
+
+static XPS_SPREAD_METHOD xps_spread_method(SkShader::TileMode tileMode) {
+ switch (tileMode) {
+ case SkShader::kClamp_TileMode:
+ return XPS_SPREAD_METHOD_PAD;
+ case SkShader::kRepeat_TileMode:
+ return XPS_SPREAD_METHOD_REPEAT;
+ case SkShader::kMirror_TileMode:
+ return XPS_SPREAD_METHOD_REFLECT;
+ default:
+ SkASSERT(!"Unknown tile mode.");
+ }
+ return XPS_SPREAD_METHOD_PAD;
+}
+
+static void transform_offsets(SkScalar* stopOffsets, const int numOffsets,
+ const SkPoint& start, const SkPoint& end,
+ const SkMatrix& transform) {
+ SkPoint startTransformed;
+ transform.mapXY(start.fX, start.fY, &startTransformed);
+ SkPoint endTransformed;
+ transform.mapXY(end.fX, end.fY, &endTransformed);
+
+ //Manhattan distance between transformed start and end.
+ SkScalar startToEnd = (endTransformed.fX - startTransformed.fX)
+ + (endTransformed.fY - startTransformed.fY);
+ if (SkScalarNearlyZero(startToEnd)) {
+ for (int i = 0; i < numOffsets; ++i) {
+ stopOffsets[i] = 0;
+ }
+ return;
+ }
+
+ for (int i = 0; i < numOffsets; ++i) {
+ SkPoint stop;
+ stop.fX = SkScalarMul(end.fX - start.fX, stopOffsets[i]);
+ stop.fY = SkScalarMul(end.fY - start.fY, stopOffsets[i]);
+
+ SkPoint stopTransformed;
+ transform.mapXY(stop.fX, stop.fY, &stopTransformed);
+
+ //Manhattan distance between transformed start and stop.
+ SkScalar startToStop = (stopTransformed.fX - startTransformed.fX)
+ + (stopTransformed.fY - startTransformed.fY);
+ //Percentage along transformed line.
+ stopOffsets[i] = SkScalarDiv(startToStop, startToEnd);
+ }
+}
+
+HRESULT SkXPSDevice::createXpsTransform(const SkMatrix& matrix,
+ IXpsOMMatrixTransform** xpsTransform) {
+ SkScalar affine[6];
+ if (!matrix.asAffine(affine)) {
+ *xpsTransform = NULL;
+ return S_FALSE;
+ }
+ XPS_MATRIX rawXpsMatrix = {
+ SkScalarToFLOAT(affine[SkMatrix::kAScaleX]),
+ SkScalarToFLOAT(affine[SkMatrix::kASkewY]),
+ SkScalarToFLOAT(affine[SkMatrix::kASkewX]),
+ SkScalarToFLOAT(affine[SkMatrix::kAScaleY]),
+ SkScalarToFLOAT(affine[SkMatrix::kATransX]),
+ SkScalarToFLOAT(affine[SkMatrix::kATransY]),
+ };
+ HRM(this->fXpsFactory->CreateMatrixTransform(&rawXpsMatrix, xpsTransform),
+ "Could not create transform.");
+
+ return S_OK;
+}
+
+HRESULT SkXPSDevice::createPath(IXpsOMGeometryFigure* figure,
+ IXpsOMVisualCollection* visuals,
+ IXpsOMPath** path) {
+ SkTScopedComPtr<IXpsOMGeometry> geometry;
+ HRM(this->fXpsFactory->CreateGeometry(&geometry),
+ "Could not create geometry.");
+
+ SkTScopedComPtr<IXpsOMGeometryFigureCollection> figureCollection;
+ HRM(geometry->GetFigures(&figureCollection), "Could not get figures.");
+ HRM(figureCollection->Append(figure), "Could not add figure.");
+
+ HRM(this->fXpsFactory->CreatePath(path), "Could not create path.");
+ HRM((*path)->SetGeometryLocal(geometry.get()), "Could not set geometry");
+
+ HRM(visuals->Append(*path), "Could not add path to visuals.");
+ return S_OK;
+}
+
+HRESULT SkXPSDevice::createXpsSolidColorBrush(const SkColor skColor,
+ const SkAlpha alpha,
+ IXpsOMBrush** xpsBrush) {
+ XPS_COLOR xpsColor = xps_color(skColor);
+ SkTScopedComPtr<IXpsOMSolidColorBrush> solidBrush;
+ HRM(this->fXpsFactory->CreateSolidColorBrush(&xpsColor, NULL, &solidBrush),
+ "Could not create solid color brush.");
+ HRM(solidBrush->SetOpacity(alpha / 255.0f), "Could not set opacity.");
+ HRM(solidBrush->QueryInterface<IXpsOMBrush>(xpsBrush), "QI Fail.");
+ return S_OK;
+}
+
+HRESULT SkXPSDevice::sideOfClamp(const SkRect& areaToFill,
+ const XPS_RECT& imageViewBox,
+ IXpsOMImageResource* image,
+ IXpsOMVisualCollection* visuals) {
+ SkTScopedComPtr<IXpsOMGeometryFigure> areaToFillFigure;
+ HR(this->createXpsRect(areaToFill, FALSE, TRUE, &areaToFillFigure));
+
+ SkTScopedComPtr<IXpsOMPath> areaToFillPath;
+ HR(this->createPath(areaToFillFigure.get(), visuals, &areaToFillPath));
+
+ SkTScopedComPtr<IXpsOMImageBrush> areaToFillBrush;
+ HRM(this->fXpsFactory->CreateImageBrush(image,
+ &imageViewBox,
+ &imageViewBox,
+ &areaToFillBrush),
+ "Could not create brush for side of clamp.");
+ HRM(areaToFillBrush->SetTileMode(XPS_TILE_MODE_FLIPXY),
+ "Could not set tile mode for side of clamp.");
+ HRM(areaToFillPath->SetFillBrushLocal(areaToFillBrush.get()),
+ "Could not set brush for side of clamp");
+
+ return S_OK;
+}
+
+HRESULT SkXPSDevice::cornerOfClamp(const SkRect& areaToFill,
+ const SkColor color,
+ IXpsOMVisualCollection* visuals) {
+ SkTScopedComPtr<IXpsOMGeometryFigure> areaToFillFigure;
+ HR(this->createXpsRect(areaToFill, FALSE, TRUE, &areaToFillFigure));
+
+ SkTScopedComPtr<IXpsOMPath> areaToFillPath;
+ HR(this->createPath(areaToFillFigure.get(), visuals, &areaToFillPath));
+
+ SkTScopedComPtr<IXpsOMBrush> areaToFillBrush;
+ HR(this->createXpsSolidColorBrush(color, 0xFF, &areaToFillBrush));
+ HRM(areaToFillPath->SetFillBrushLocal(areaToFillBrush.get()),
+ "Could not set brush for corner of clamp.");
+
+ return S_OK;
+}
+
+static const XPS_TILE_MODE XTM_N = XPS_TILE_MODE_NONE;
+static const XPS_TILE_MODE XTM_T = XPS_TILE_MODE_TILE;
+static const XPS_TILE_MODE XTM_X = XPS_TILE_MODE_FLIPX;
+static const XPS_TILE_MODE XTM_Y = XPS_TILE_MODE_FLIPY;
+static const XPS_TILE_MODE XTM_XY = XPS_TILE_MODE_FLIPXY;
+
+//TODO(bungeman): In the future, should skia add None,
+//handle None+Mirror and None+Repeat correctly.
+//None is currently an internal hack so masks don't repeat (None+None only).
+static XPS_TILE_MODE SkToXpsTileMode[SkShader::kTileModeCount+1]
+ [SkShader::kTileModeCount+1] = {
+ //Clamp //Repeat //Mirror //None
+/*Clamp */ XTM_N, XTM_T, XTM_Y, XTM_N,
+/*Repeat*/ XTM_T, XTM_T, XTM_Y, XTM_N,
+/*Mirror*/ XTM_X, XTM_X, XTM_XY, XTM_X,
+/*None */ XTM_N, XTM_N, XTM_Y, XTM_N,
+};
+
+HRESULT SkXPSDevice::createXpsImageBrush(
+ const SkBitmap& bitmap,
+ const SkMatrix& localMatrix,
+ const SkShader::TileMode (&xy)[2],
+ const SkAlpha alpha,
+ IXpsOMTileBrush** xpsBrush) {
+ SkDynamicMemoryWStream write;
+ if (!SkImageEncoder::EncodeStream(&write, bitmap,
+ SkImageEncoder::kPNG_Type, 100)) {
+ HRM(E_FAIL, "Unable to encode bitmap as png.");
+ }
+ SkMemoryStream* read = new SkMemoryStream;
+ read->setData(write.copyToData())->unref();
+ SkTScopedComPtr<IStream> readWrapper;
+ HRM(SkIStream::CreateFromSkStream(read, true, &readWrapper),
+ "Could not create stream from png data.");
+
+ const size_t size =
+ SK_ARRAY_COUNT(L"/Documents/1/Resources/Images/" L_GUID_ID L".png");
+ wchar_t buffer[size];
+ wchar_t id[GUID_ID_LEN];
+ HR(create_id(id, GUID_ID_LEN));
+ swprintf_s(buffer, size, L"/Documents/1/Resources/Images/%s.png", id);
+
+ SkTScopedComPtr<IOpcPartUri> imagePartUri;
+ HRM(this->fXpsFactory->CreatePartUri(buffer, &imagePartUri),
+ "Could not create image part uri.");
+
+ SkTScopedComPtr<IXpsOMImageResource> imageResource;
+ HRM(this->fXpsFactory->CreateImageResource(
+ readWrapper.get(),
+ XPS_IMAGE_TYPE_PNG,
+ imagePartUri.get(),
+ &imageResource),
+ "Could not create image resource.");
+
+ XPS_RECT bitmapRect = {
+ 0.0, 0.0,
+ static_cast<FLOAT>(bitmap.width()), static_cast<FLOAT>(bitmap.height())
+ };
+ SkTScopedComPtr<IXpsOMImageBrush> xpsImageBrush;
+ HRM(this->fXpsFactory->CreateImageBrush(imageResource.get(),
+ &bitmapRect, &bitmapRect,
+ &xpsImageBrush),
+ "Could not create image brush.");
+
+ if (SkShader::kClamp_TileMode != xy[0] &&
+ SkShader::kClamp_TileMode != xy[1]) {
+
+ HRM(xpsImageBrush->SetTileMode(SkToXpsTileMode[xy[0]][xy[1]]),
+ "Could not set image tile mode");
+ HRM(xpsImageBrush->SetOpacity(alpha / 255.0f),
+ "Could not set image opacity.");
+ HRM(xpsImageBrush->QueryInterface(xpsBrush), "QI failed.");
+ } else {
+ //TODO(bungeman): compute how big this really needs to be.
+ const SkScalar BIG = SkIntToScalar(1000); //SK_ScalarMax;
+ const FLOAT BIG_F = SkScalarToFLOAT(BIG);
+ const SkScalar bWidth = SkIntToScalar(bitmap.width());
+ const SkScalar bHeight = SkIntToScalar(bitmap.height());
+
+ //create brush canvas
+ SkTScopedComPtr<IXpsOMCanvas> brushCanvas;
+ HRM(this->fXpsFactory->CreateCanvas(&brushCanvas),
+ "Could not create image brush canvas.");
+ SkTScopedComPtr<IXpsOMVisualCollection> brushVisuals;
+ HRM(brushCanvas->GetVisuals(&brushVisuals),
+ "Could not get image brush canvas visuals collection.");
+
+ //create central figure
+ const SkRect bitmapPoints = SkRect::MakeLTRB(0, 0, bWidth, bHeight);
+ SkTScopedComPtr<IXpsOMGeometryFigure> centralFigure;
+ HR(this->createXpsRect(bitmapPoints, FALSE, TRUE, &centralFigure));
+
+ SkTScopedComPtr<IXpsOMPath> centralPath;
+ HR(this->createPath(centralFigure.get(),
+ brushVisuals.get(),
+ &centralPath));
+ HRM(xpsImageBrush->SetTileMode(XPS_TILE_MODE_FLIPXY),
+ "Could not set tile mode for image brush central path.");
+ HRM(centralPath->SetFillBrushLocal(xpsImageBrush.get()),
+ "Could not set fill brush for image brush central path.");
+
+ //add left/right
+ if (SkShader::kClamp_TileMode == xy[0]) {
+ SkRect leftArea = SkRect::MakeLTRB(-BIG, 0, 0, bHeight);
+ XPS_RECT leftImageViewBox = {
+ 0.0, 0.0,
+ 1.0, static_cast<FLOAT>(bitmap.height()),
+ };
+ HR(this->sideOfClamp(leftArea, leftImageViewBox,
+ imageResource.get(),
+ brushVisuals.get()));
+
+ SkRect rightArea = SkRect::MakeLTRB(bWidth, 0, BIG, bHeight);
+ XPS_RECT rightImageViewBox = {
+ bitmap.width() - 1.0f, 0.0f,
+ 1.0f, static_cast<FLOAT>(bitmap.height()),
+ };
+ HR(this->sideOfClamp(rightArea, rightImageViewBox,
+ imageResource.get(),
+ brushVisuals.get()));
+ }
+
+ //add top/bottom
+ if (SkShader::kClamp_TileMode == xy[1]) {
+ SkRect topArea = SkRect::MakeLTRB(0, -BIG, bWidth, 0);
+ XPS_RECT topImageViewBox = {
+ 0.0, 0.0,
+ static_cast<FLOAT>(bitmap.width()), 1.0,
+ };
+ HR(this->sideOfClamp(topArea, topImageViewBox,
+ imageResource.get(),
+ brushVisuals.get()));
+
+ SkRect bottomArea = SkRect::MakeLTRB(0, bHeight, bWidth, BIG);
+ XPS_RECT bottomImageViewBox = {
+ 0.0f, bitmap.height() - 1.0f,
+ static_cast<FLOAT>(bitmap.width()), 1.0f,
+ };
+ HR(this->sideOfClamp(bottomArea, bottomImageViewBox,
+ imageResource.get(),
+ brushVisuals.get()));
+ }
+
+ //add tl, tr, bl, br
+ if (SkShader::kClamp_TileMode == xy[0] &&
+ SkShader::kClamp_TileMode == xy[1]) {
+
+ SkAutoLockPixels alp(bitmap);
+
+ const SkColor tlColor = bitmap.getColor(0,0);
+ const SkRect tlArea = SkRect::MakeLTRB(-BIG, -BIG, 0, 0);
+ HR(this->cornerOfClamp(tlArea, tlColor, brushVisuals.get()));
+
+ const SkColor trColor = bitmap.getColor(bitmap.width()-1,0);
+ const SkRect trArea = SkRect::MakeLTRB(bWidth, -BIG, BIG, 0);
+ HR(this->cornerOfClamp(trArea, trColor, brushVisuals.get()));
+
+ const SkColor brColor = bitmap.getColor(bitmap.width()-1,
+ bitmap.height()-1);
+ const SkRect brArea = SkRect::MakeLTRB(bWidth, bHeight, BIG, BIG);
+ HR(this->cornerOfClamp(brArea, brColor, brushVisuals.get()));
+
+ const SkColor blColor = bitmap.getColor(0,bitmap.height()-1);
+ const SkRect blArea = SkRect::MakeLTRB(-BIG, bHeight, 0, BIG);
+ HR(this->cornerOfClamp(blArea, blColor, brushVisuals.get()));
+ }
+
+ //create visual brush from canvas
+ XPS_RECT bound = {};
+ if (SkShader::kClamp_TileMode == xy[0] &&
+ SkShader::kClamp_TileMode == xy[1]) {
+
+ bound.x = BIG_F / -2;
+ bound.y = BIG_F / -2;
+ bound.width = BIG_F;
+ bound.height = BIG_F;
+ } else if (SkShader::kClamp_TileMode == xy[0]) {
+ bound.x = BIG_F / -2;
+ bound.y = 0.0f;
+ bound.width = BIG_F;
+ bound.height = static_cast<FLOAT>(bitmap.height());
+ } else if (SkShader::kClamp_TileMode == xy[1]) {
+ bound.x = 0;
+ bound.y = BIG_F / -2;
+ bound.width = static_cast<FLOAT>(bitmap.width());
+ bound.height = BIG_F;
+ }
+ SkTScopedComPtr<IXpsOMVisualBrush> clampBrush;
+ HRM(this->fXpsFactory->CreateVisualBrush(&bound, &bound, &clampBrush),
+ "Could not create visual brush for image brush.");
+ HRM(clampBrush->SetVisualLocal(brushCanvas.get()),
+ "Could not set canvas on visual brush for image brush.");
+ HRM(clampBrush->SetTileMode(SkToXpsTileMode[xy[0]][xy[1]]),
+ "Could not set tile mode on visual brush for image brush.");
+ HRM(clampBrush->SetOpacity(alpha / 255.0f),
+ "Could not set opacity on visual brush for image brush.");
+
+ HRM(clampBrush->QueryInterface(xpsBrush), "QI failed.");
+ }
+
+ SkTScopedComPtr<IXpsOMMatrixTransform> xpsMatrixToUse;
+ HR(this->createXpsTransform(localMatrix, &xpsMatrixToUse));
+ if (NULL != xpsMatrixToUse.get()) {
+ HRM((*xpsBrush)->SetTransformLocal(xpsMatrixToUse.get()),
+ "Could not set transform for image brush.");
+ } else {
+ //TODO(bungeman): perspective bitmaps in general.
+ }
+
+ return S_OK;
+}
+
+HRESULT SkXPSDevice::createXpsGradientStop(const SkColor skColor,
+ const SkScalar offset,
+ IXpsOMGradientStop** xpsGradStop) {
+ XPS_COLOR gradStopXpsColor = xps_color(skColor);
+ HRM(this->fXpsFactory->CreateGradientStop(&gradStopXpsColor,
+ NULL,
+ SkScalarToFLOAT(offset),
+ xpsGradStop),
+ "Could not create gradient stop.");
+ return S_OK;
+}
+
+HRESULT SkXPSDevice::createXpsLinearGradient(SkShader::GradientInfo info,
+ const SkAlpha alpha,
+ const SkMatrix& localMatrix,
+ IXpsOMMatrixTransform* xpsMatrix,
+ IXpsOMBrush** xpsBrush) {
+ XPS_POINT startPoint;
+ XPS_POINT endPoint;
+ if (NULL != xpsMatrix) {
+ startPoint = xps_point(info.fPoint[0]);
+ endPoint = xps_point(info.fPoint[1]);
+ } else {
+ transform_offsets(info.fColorOffsets, info.fColorCount,
+ info.fPoint[0], info.fPoint[1],
+ localMatrix);
+ startPoint = xps_point(info.fPoint[0], localMatrix);
+ endPoint = xps_point(info.fPoint[1], localMatrix);
+ }
+
+ SkTScopedComPtr<IXpsOMGradientStop> gradStop0;
+ HR(createXpsGradientStop(info.fColors[0],
+ info.fColorOffsets[0],
+ &gradStop0));
+
+ SkTScopedComPtr<IXpsOMGradientStop> gradStop1;
+ HR(createXpsGradientStop(info.fColors[1],
+ info.fColorOffsets[1],
+ &gradStop1));
+
+ SkTScopedComPtr<IXpsOMLinearGradientBrush> gradientBrush;
+ HRM(this->fXpsFactory->CreateLinearGradientBrush(gradStop0.get(),
+ gradStop1.get(),
+ &startPoint,
+ &endPoint,
+ &gradientBrush),
+ "Could not create linear gradient brush.");
+ if (NULL != xpsMatrix) {
+ HRM(gradientBrush->SetTransformLocal(xpsMatrix),
+ "Could not set transform on linear gradient brush.");
+ }
+
+ SkTScopedComPtr<IXpsOMGradientStopCollection> gradStopCollection;
+ HRM(gradientBrush->GetGradientStops(&gradStopCollection),
+ "Could not get linear gradient stop collection.");
+ for (int i = 2; i < info.fColorCount; ++i) {
+ SkTScopedComPtr<IXpsOMGradientStop> gradStop;
+ HR(createXpsGradientStop(info.fColors[i],
+ info.fColorOffsets[i],
+ &gradStop));
+ HRM(gradStopCollection->Append(gradStop.get()),
+ "Could not add linear gradient stop.");
+ }
+
+ HRM(gradientBrush->SetSpreadMethod(xps_spread_method(info.fTileMode)),
+ "Could not set spread method of linear gradient.");
+
+ HRM(gradientBrush->SetOpacity(alpha / 255.0f),
+ "Could not set opacity of linear gradient brush.");
+ HRM(gradientBrush->QueryInterface<IXpsOMBrush>(xpsBrush), "QI failed");
+
+ return S_OK;
+}
+
+HRESULT SkXPSDevice::createXpsRadialGradient(SkShader::GradientInfo info,
+ const SkAlpha alpha,
+ const SkMatrix& localMatrix,
+ IXpsOMMatrixTransform* xpsMatrix,
+ IXpsOMBrush** xpsBrush) {
+ SkTScopedComPtr<IXpsOMGradientStop> gradStop0;
+ HR(createXpsGradientStop(info.fColors[0],
+ info.fColorOffsets[0],
+ &gradStop0));
+
+ SkTScopedComPtr<IXpsOMGradientStop> gradStop1;
+ HR(createXpsGradientStop(info.fColors[1],
+ info.fColorOffsets[1],
+ &gradStop1));
+
+ //TODO: figure out how to fake better if not affine
+ XPS_POINT centerPoint;
+ XPS_POINT gradientOrigin;
+ XPS_SIZE radiiSizes;
+ if (NULL != xpsMatrix) {
+ centerPoint = xps_point(info.fPoint[0]);
+ gradientOrigin = xps_point(info.fPoint[0]);
+ radiiSizes.width = SkScalarToFLOAT(info.fRadius[0]);
+ radiiSizes.height = SkScalarToFLOAT(info.fRadius[0]);
+ } else {
+ centerPoint = xps_point(info.fPoint[0], localMatrix);
+ gradientOrigin = xps_point(info.fPoint[0], localMatrix);
+
+ SkScalar radius = info.fRadius[0];
+ SkVector vec[2];
+
+ vec[0].set(radius, 0);
+ vec[1].set(0, radius);
+ localMatrix.mapVectors(vec, 2);
+
+ SkScalar d0 = vec[0].length();
+ SkScalar d1 = vec[1].length();
+
+ radiiSizes.width = SkScalarToFLOAT(d0);
+ radiiSizes.height = SkScalarToFLOAT(d1);
+ }
+
+ SkTScopedComPtr<IXpsOMRadialGradientBrush> gradientBrush;
+ HRM(this->fXpsFactory->CreateRadialGradientBrush(gradStop0.get(),
+ gradStop1.get(),
+ &centerPoint,
+ &gradientOrigin,
+ &radiiSizes,
+ &gradientBrush),
+ "Could not create radial gradient brush.");
+ if (NULL != xpsMatrix) {
+ HRM(gradientBrush->SetTransformLocal(xpsMatrix),
+ "Could not set transform on radial gradient brush.");
+ }
+
+ SkTScopedComPtr<IXpsOMGradientStopCollection> gradStopCollection;
+ HRM(gradientBrush->GetGradientStops(&gradStopCollection),
+ "Could not get radial gradient stop collection.");
+ for (int i = 2; i < info.fColorCount; ++i) {
+ SkTScopedComPtr<IXpsOMGradientStop> gradStop;
+ HR(createXpsGradientStop(info.fColors[i],
+ info.fColorOffsets[i],
+ &gradStop));
+ HRM(gradStopCollection->Append(gradStop.get()),
+ "Could not add radial gradient stop.");
+ }
+
+ HRM(gradientBrush->SetSpreadMethod(xps_spread_method(info.fTileMode)),
+ "Could not set spread method of radial gradient.");
+
+ HRM(gradientBrush->SetOpacity(alpha / 255.0f),
+ "Could not set opacity of radial gradient brush.");
+ HRM(gradientBrush->QueryInterface<IXpsOMBrush>(xpsBrush), "QI failed.");
+
+ return S_OK;
+}
+
+HRESULT SkXPSDevice::createXpsBrush(const SkPaint& skPaint,
+ IXpsOMBrush** brush,
+ const SkMatrix* parentTransform) {
+ const SkShader *shader = skPaint.getShader();
+ if (NULL == shader) {
+ HR(this->createXpsSolidColorBrush(skPaint.getColor(), 0xFF, brush));
+ return S_OK;
+ }
+
+ //Gradient shaders.
+ SkShader::GradientInfo info;
+ info.fColorCount = 0;
+ info.fColors = NULL;
+ info.fColorOffsets = NULL;
+ SkShader::GradientType gradientType = shader->asAGradient(&info);
+
+ if (SkShader::kNone_GradientType == gradientType) {
+ //Nothing to see, move along.
+
+ } else if (SkShader::kColor_GradientType == gradientType) {
+ SkASSERT(1 == info.fColorCount);
+ SkColor color;
+ info.fColors = &color;
+ SkShader::GradientType gradientType = shader->asAGradient(&info);
+ SkAlpha alpha = skPaint.getAlpha();
+ HR(this->createXpsSolidColorBrush(color, alpha, brush));
+ return S_OK;
+
+ } else {
+ if (info.fColorCount == 0) {
+ const SkColor color = skPaint.getColor();
+ HR(this->createXpsSolidColorBrush(color, 0xFF, brush));
+ return S_OK;
+ }
+
+ SkAutoTArray<SkColor> colors(info.fColorCount);
+ SkAutoTArray<SkScalar> colorOffsets(info.fColorCount);
+ info.fColors = colors.get();
+ info.fColorOffsets = colorOffsets.get();
+ shader->asAGradient(&info);
+
+ if (1 == info.fColorCount) {
+ SkColor color = info.fColors[0];
+ SkAlpha alpha = skPaint.getAlpha();
+ HR(this->createXpsSolidColorBrush(color, alpha, brush));
+ return S_OK;
+ }
+
+ SkMatrix localMatrix = shader->getLocalMatrix();
+ if (NULL != parentTransform) {
+ localMatrix.preConcat(*parentTransform);
+ }
+ SkTScopedComPtr<IXpsOMMatrixTransform> xpsMatrixToUse;
+ HR(this->createXpsTransform(localMatrix, &xpsMatrixToUse));
+
+ if (SkShader::kLinear_GradientType == gradientType) {
+ HR(this->createXpsLinearGradient(info,
+ skPaint.getAlpha(),
+ localMatrix,
+ xpsMatrixToUse.get(),
+ brush));
+ return S_OK;
+ }
+
+ if (SkShader::kRadial_GradientType == gradientType) {
+ HR(this->createXpsRadialGradient(info,
+ skPaint.getAlpha(),
+ localMatrix,
+ xpsMatrixToUse.get(),
+ brush));
+ return S_OK;
+ }
+
+ if (SkShader::kRadial2_GradientType == gradientType ||
+ SkShader::kConical_GradientType == gradientType) {
+ //simple if affine and one is 0, otherwise will have to fake
+ }
+
+ if (SkShader::kSweep_GradientType == gradientType) {
+ //have to fake
+ }
+ }
+
+ SkBitmap outTexture;
+ SkMatrix outMatrix;
+ SkShader::TileMode xy[2];
+ SkShader::BitmapType bitmapType = shader->asABitmap(&outTexture,
+ &outMatrix,
+ xy);
+ switch (bitmapType) {
+ case SkShader::kNone_BitmapType:
+ break;
+ case SkShader::kDefault_BitmapType: {
+ //TODO: outMatrix??
+ SkMatrix localMatrix = shader->getLocalMatrix();
+ if (NULL != parentTransform) {
+ localMatrix.preConcat(*parentTransform);
+ }
+
+ SkTScopedComPtr<IXpsOMTileBrush> tileBrush;
+ HR(this->createXpsImageBrush(outTexture,
+ localMatrix,
+ xy,
+ skPaint.getAlpha(),
+ &tileBrush));
+
+ HRM(tileBrush->QueryInterface<IXpsOMBrush>(brush), "QI failed.");
+
+ return S_OK;
+ }
+ case SkShader::kRadial_BitmapType:
+ case SkShader::kSweep_BitmapType:
+ case SkShader::kTwoPointRadial_BitmapType:
+ default:
+ break;
+ }
+
+ HR(this->createXpsSolidColorBrush(skPaint.getColor(), 0xFF, brush));
+ return S_OK;
+}
+
+static bool rect_must_be_pathed(const SkPaint& paint, const SkMatrix& matrix) {
+ const bool zeroWidth = (0 == paint.getStrokeWidth());
+ const bool stroke = (SkPaint::kFill_Style != paint.getStyle());
+
+ return paint.getPathEffect() ||
+ paint.getMaskFilter() ||
+ paint.getRasterizer() ||
+ (stroke && (
+ (matrix.hasPerspective() && !zeroWidth) ||
+ SkPaint::kMiter_Join != paint.getStrokeJoin() ||
+ (SkPaint::kMiter_Join == paint.getStrokeJoin() &&
+ paint.getStrokeMiter() < SK_ScalarSqrt2)
+ ))
+ ;
+}
+
+HRESULT SkXPSDevice::createXpsRect(const SkRect& rect, BOOL stroke, BOOL fill,
+ IXpsOMGeometryFigure** xpsRect) {
+ const SkPoint points[4] = {
+ { rect.fLeft, rect.fTop },
+ { rect.fRight, rect.fTop },
+ { rect.fRight, rect.fBottom },
+ { rect.fLeft, rect.fBottom },
+ };
+ return this->createXpsQuad(points, stroke, fill, xpsRect);
+}
+HRESULT SkXPSDevice::createXpsQuad(const SkPoint (&points)[4],
+ BOOL stroke, BOOL fill,
+ IXpsOMGeometryFigure** xpsQuad) {
+ // Define the start point.
+ XPS_POINT startPoint = xps_point(points[0]);
+
+ // Create the figure.
+ HRM(this->fXpsFactory->CreateGeometryFigure(&startPoint, xpsQuad),
+ "Could not create quad geometry figure.");
+
+ // Define the type of each segment.
+ XPS_SEGMENT_TYPE segmentTypes[3] = {
+ XPS_SEGMENT_TYPE_LINE,
+ XPS_SEGMENT_TYPE_LINE,
+ XPS_SEGMENT_TYPE_LINE,
+ };
+
+ // Define the x and y coordinates of each corner of the figure.
+ FLOAT segmentData[6] = {
+ SkScalarToFLOAT(points[1].fX), SkScalarToFLOAT(points[1].fY),
+ SkScalarToFLOAT(points[2].fX), SkScalarToFLOAT(points[2].fY),
+ SkScalarToFLOAT(points[3].fX), SkScalarToFLOAT(points[3].fY),
+ };
+
+ // Describe if the segments are stroked.
+ BOOL segmentStrokes[3] = {
+ stroke, stroke, stroke,
+ };
+
+ // Add the segment data to the figure.
+ HRM((*xpsQuad)->SetSegments(
+ 3, 6,
+ segmentTypes , segmentData, segmentStrokes),
+ "Could not add segment data to quad.");
+
+ // Set the closed and filled properties of the figure.
+ HRM((*xpsQuad)->SetIsClosed(stroke), "Could not set quad close.");
+ HRM((*xpsQuad)->SetIsFilled(fill), "Could not set quad fill.");
+
+ return S_OK;
+}
+
+uint32_t SkXPSDevice::getDeviceCapabilities() {
+ return kVector_Capability;
+}
+
+void SkXPSDevice::clear(SkColor color) {
+ //TODO: override this for XPS
+ SkDEBUGF(("XPS clear not yet implemented."));
+}
+
+void SkXPSDevice::drawPoints(const SkDraw& d, SkCanvas::PointMode mode,
+ size_t count, const SkPoint points[],
+ const SkPaint& paint) {
+ //This will call back into the device to do the drawing.
+ d.drawPoints(mode, count, points, paint, true);
+}
+
+void SkXPSDevice::drawVertices(const SkDraw&, SkCanvas::VertexMode,
+ int vertexCount, const SkPoint verts[],
+ const SkPoint texs[], const SkColor colors[],
+ SkXfermode* xmode, const uint16_t indices[],
+ int indexCount, const SkPaint& paint) {
+ //TODO: override this for XPS
+ SkDEBUGF(("XPS drawVertices not yet implemented."));
+}
+
+void SkXPSDevice::drawPaint(const SkDraw& d, const SkPaint& origPaint) {
+ const SkRect r = SkRect::MakeSize(this->fCurrentCanvasSize);
+
+ //If trying to paint with a stroke, ignore that and fill.
+ SkPaint* fillPaint = const_cast<SkPaint*>(&origPaint);
+ SkTCopyOnFirstWrite<SkPaint> paint(origPaint);
+ if (paint->getStyle() != SkPaint::kFill_Style) {
+ paint.writable()->setStyle(SkPaint::kFill_Style);
+ }
+
+ this->internalDrawRect(d, r, false, *fillPaint);
+}
+
+void SkXPSDevice::drawRect(const SkDraw& d,
+ const SkRect& r,
+ const SkPaint& paint) {
+ this->internalDrawRect(d, r, true, paint);
+}
+
+void SkXPSDevice::internalDrawRect(const SkDraw& d,
+ const SkRect& r,
+ bool transformRect,
+ const SkPaint& paint) {
+ //Exit early if there is nothing to draw.
+ if (d.fClip->isEmpty() ||
+ (paint.getAlpha() == 0 && paint.getXfermode() == NULL)) {
+ return;
+ }
+
+ //Path the rect if we can't optimize it.
+ if (rect_must_be_pathed(paint, *d.fMatrix)) {
+ SkPath tmp;
+ tmp.addRect(r);
+ tmp.setFillType(SkPath::kWinding_FillType);
+ this->drawPath(d, tmp, paint, NULL, true);
+ return;
+ }
+
+ //Create the shaded path.
+ SkTScopedComPtr<IXpsOMPath> shadedPath;
+ HRVM(this->fXpsFactory->CreatePath(&shadedPath),
+ "Could not create shaded path for rect.");
+
+ //Create the shaded geometry.
+ SkTScopedComPtr<IXpsOMGeometry> shadedGeometry;
+ HRVM(this->fXpsFactory->CreateGeometry(&shadedGeometry),
+ "Could not create shaded geometry for rect.");
+
+ //Add the geometry to the shaded path.
+ HRVM(shadedPath->SetGeometryLocal(shadedGeometry.get()),
+ "Could not set shaded geometry for rect.");
+
+ //Set the brushes.
+ BOOL fill = FALSE;
+ BOOL stroke = FALSE;
+ HRV(this->shadePath(shadedPath.get(), paint, *d.fMatrix, &fill, &stroke));
+
+ bool xpsTransformsPath = true;
+ //Transform the geometry.
+ if (transformRect && xpsTransformsPath) {
+ SkTScopedComPtr<IXpsOMMatrixTransform> xpsTransform;
+ HRV(this->createXpsTransform(*d.fMatrix, &xpsTransform));
+ if (xpsTransform.get()) {
+ HRVM(shadedGeometry->SetTransformLocal(xpsTransform.get()),
+ "Could not set transform for rect.");
+ } else {
+ xpsTransformsPath = false;
+ }
+ }
+
+ //Create the figure.
+ SkTScopedComPtr<IXpsOMGeometryFigure> rectFigure;
+ {
+ SkPoint points[4] = {
+ { r.fLeft, r.fTop },
+ { r.fLeft, r.fBottom },
+ { r.fRight, r.fBottom },
+ { r.fRight, r.fTop },
+ };
+ if (!xpsTransformsPath && transformRect) {
+ d.fMatrix->mapPoints(points, SK_ARRAY_COUNT(points));
+ }
+ HRV(this->createXpsQuad(points, stroke, fill, &rectFigure));
+ }
+
+ //Get the figures of the shaded geometry.
+ SkTScopedComPtr<IXpsOMGeometryFigureCollection> shadedFigures;
+ HRVM(shadedGeometry->GetFigures(&shadedFigures),
+ "Could not get shaded figures for rect.");
+
+ //Add the figure to the shaded geometry figures.
+ HRVM(shadedFigures->Append(rectFigure.get()),
+ "Could not add shaded figure for rect.");
+
+ HRV(this->clip(shadedPath.get(), d));
+
+ //Add the shaded path to the current visuals.
+ SkTScopedComPtr<IXpsOMVisualCollection> currentVisuals;
+ HRVM(this->fCurrentXpsCanvas->GetVisuals(&currentVisuals),
+ "Could not get current visuals for rect.");
+ HRVM(currentVisuals->Append(shadedPath.get()),
+ "Could not add rect to current visuals.");
+}
+
+static HRESULT close_figure(const SkTDArray<XPS_SEGMENT_TYPE>& segmentTypes,
+ const SkTDArray<BOOL>& segmentStrokes,
+ const SkTDArray<FLOAT>& segmentData,
+ BOOL stroke, BOOL fill,
+ IXpsOMGeometryFigure* figure,
+ IXpsOMGeometryFigureCollection* figures) {
+ // Add the segment data to the figure.
+ HRM(figure->SetSegments(segmentTypes.count(), segmentData.count(),
+ segmentTypes.begin() , segmentData.begin(),
+ segmentStrokes.begin()),
+ "Could not set path segments.");
+
+ // Set the closed and filled properties of the figure.
+ HRM(figure->SetIsClosed(stroke), "Could not set path closed.");
+ HRM(figure->SetIsFilled(fill), "Could not set path fill.");
+
+ // Add the figure created above to this geometry.
+ HRM(figures->Append(figure), "Could not add path to geometry.");
+ return S_OK;
+}
+
+HRESULT SkXPSDevice::addXpsPathGeometry(
+ IXpsOMGeometryFigureCollection* xpsFigures,
+ BOOL stroke, BOOL fill, const SkPath& path) {
+ SkTDArray<XPS_SEGMENT_TYPE> segmentTypes;
+ SkTDArray<BOOL> segmentStrokes;
+ SkTDArray<FLOAT> segmentData;
+
+ SkTScopedComPtr<IXpsOMGeometryFigure> xpsFigure;
+ SkPath::Iter iter(path, true);
+ SkPoint points[4];
+ SkPath::Verb verb;
+ while ((verb = iter.next(points)) != SkPath::kDone_Verb) {
+ switch (verb) {
+ case SkPath::kMove_Verb: {
+ if (NULL != xpsFigure.get()) {
+ HR(close_figure(segmentTypes, segmentStrokes, segmentData,
+ stroke, fill,
+ xpsFigure.get() , xpsFigures));
+ xpsFigure.reset();
+ segmentTypes.rewind();
+ segmentStrokes.rewind();
+ segmentData.rewind();
+ }
+ // Define the start point.
+ XPS_POINT startPoint = xps_point(points[0]);
+ // Create the figure.
+ HRM(this->fXpsFactory->CreateGeometryFigure(&startPoint,
+ &xpsFigure),
+ "Could not create path geometry figure.");
+ break;
+ }
+ case SkPath::kLine_Verb:
+ if (iter.isCloseLine()) break; //ignore the line, auto-closed
+ segmentTypes.push(XPS_SEGMENT_TYPE_LINE);
+ segmentStrokes.push(stroke);
+ segmentData.push(SkScalarToFLOAT(points[1].fX));
+ segmentData.push(SkScalarToFLOAT(points[1].fY));
+ break;
+ case SkPath::kQuad_Verb:
+ segmentTypes.push(XPS_SEGMENT_TYPE_QUADRATIC_BEZIER);
+ segmentStrokes.push(stroke);
+ segmentData.push(SkScalarToFLOAT(points[1].fX));
+ segmentData.push(SkScalarToFLOAT(points[1].fY));
+ segmentData.push(SkScalarToFLOAT(points[2].fX));
+ segmentData.push(SkScalarToFLOAT(points[2].fY));
+ break;
+ case SkPath::kCubic_Verb:
+ segmentTypes.push(XPS_SEGMENT_TYPE_BEZIER);
+ segmentStrokes.push(stroke);
+ segmentData.push(SkScalarToFLOAT(points[1].fX));
+ segmentData.push(SkScalarToFLOAT(points[1].fY));
+ segmentData.push(SkScalarToFLOAT(points[2].fX));
+ segmentData.push(SkScalarToFLOAT(points[2].fY));
+ segmentData.push(SkScalarToFLOAT(points[3].fX));
+ segmentData.push(SkScalarToFLOAT(points[3].fY));
+ break;
+ case SkPath::kClose_Verb:
+ // we ignore these, and just get the whole segment from
+ // the corresponding line/quad/cubic verbs
+ break;
+ default:
+ SkASSERT(!"unexpected verb");
+ break;
+ }
+ }
+ if (NULL != xpsFigure.get()) {
+ HR(close_figure(segmentTypes, segmentStrokes, segmentData,
+ stroke, fill,
+ xpsFigure.get(), xpsFigures));
+ }
+ return S_OK;
+}
+
+HRESULT SkXPSDevice::drawInverseWindingPath(const SkDraw& d,
+ const SkPath& devicePath,
+ IXpsOMPath* shadedPath) {
+ const SkRect universeRect = SkRect::MakeLTRB(0, 0,
+ this->fCurrentCanvasSize.fWidth, this->fCurrentCanvasSize.fHeight);
+
+ const XPS_RECT universeRectXps = {
+ 0.0f, 0.0f,
+ SkScalarToFLOAT(this->fCurrentCanvasSize.fWidth),
+ SkScalarToFLOAT(this->fCurrentCanvasSize.fHeight),
+ };
+
+ //Get the geometry.
+ SkTScopedComPtr<IXpsOMGeometry> shadedGeometry;
+ HRM(shadedPath->GetGeometry(&shadedGeometry),
+ "Could not get shaded geometry for inverse path.");
+
+ //Get the figures from the geometry.
+ SkTScopedComPtr<IXpsOMGeometryFigureCollection> shadedFigures;
+ HRM(shadedGeometry->GetFigures(&shadedFigures),
+ "Could not get shaded figures for inverse path.");
+
+ HRM(shadedGeometry->SetFillRule(XPS_FILL_RULE_NONZERO),
+ "Could not set shaded fill rule for inverse path.");
+
+ //Take everything drawn so far, and make a shared resource out of it.
+ //Replace everything drawn so far with
+ //inverse canvas
+ // old canvas of everything so far
+ // world shaded figure, clipped to current clip
+ // top canvas of everything so far, clipped to path
+ //Note: this is not quite right when there is nothing solid in the
+ //canvas of everything so far, as the bit on top will allow
+ //the world paint to show through.
+
+ //Create new canvas.
+ SkTScopedComPtr<IXpsOMCanvas> newCanvas;
+ HRM(this->fXpsFactory->CreateCanvas(&newCanvas),
+ "Could not create inverse canvas.");
+
+ //Save the old canvas to a dictionary on the new canvas.
+ SkTScopedComPtr<IXpsOMDictionary> newDictionary;
+ HRM(this->fXpsFactory->CreateDictionary(&newDictionary),
+ "Could not create inverse dictionary.");
+ HRM(newCanvas->SetDictionaryLocal(newDictionary.get()),
+ "Could not set inverse dictionary.");
+
+ const size_t size = SK_ARRAY_COUNT(L"ID" L_GUID_ID);
+ wchar_t buffer[size];
+ wchar_t id[GUID_ID_LEN];
+ HR(create_id(id, GUID_ID_LEN, '_'));
+ swprintf_s(buffer, size, L"ID%s", id);
+ HRM(newDictionary->Append(buffer, this->fCurrentXpsCanvas.get()),
+ "Could not add canvas to inverse dictionary.");
+
+ //Start drawing
+ SkTScopedComPtr<IXpsOMVisualCollection> newVisuals;
+ HRM(newCanvas->GetVisuals(&newVisuals),
+ "Could not get inverse canvas visuals.");
+
+ //Draw old canvas from dictionary onto new canvas.
+ SkTScopedComPtr<IXpsOMGeometry> oldGeometry;
+ HRM(this->fXpsFactory->CreateGeometry(&oldGeometry),
+ "Could not create old inverse geometry.");
+
+ SkTScopedComPtr<IXpsOMGeometryFigureCollection> oldFigures;
+ HRM(oldGeometry->GetFigures(&oldFigures),
+ "Could not get old inverse figures.");
+
+ SkTScopedComPtr<IXpsOMGeometryFigure> oldFigure;
+ HR(this->createXpsRect(universeRect, FALSE, TRUE, &oldFigure));
+ HRM(oldFigures->Append(oldFigure.get()),
+ "Could not add old inverse figure.");
+
+ SkTScopedComPtr<IXpsOMVisualBrush> oldBrush;
+ HRM(this->fXpsFactory->CreateVisualBrush(&universeRectXps,
+ &universeRectXps,
+ &oldBrush),
+ "Could not create old inverse brush.");
+
+ SkTScopedComPtr<IXpsOMPath> oldPath;
+ HRM(this->fXpsFactory->CreatePath(&oldPath),
+ "Could not create old inverse path.");
+ HRM(oldPath->SetGeometryLocal(oldGeometry.get()),
+ "Could not set old inverse geometry.");
+ HRM(oldPath->SetFillBrushLocal(oldBrush.get()),
+ "Could not set old inverse fill brush.");
+ //the brush must be parented before setting the lookup.
+ HRM(newVisuals->Append(oldPath.get()),
+ "Could not add old inverse path to new canvas visuals.");
+ HRM(oldBrush->SetVisualLookup(buffer),
+ "Could not set old inverse brush visual lookup.");
+
+ //Draw the clip filling shader.
+ SkTScopedComPtr<IXpsOMGeometryFigure> shadedFigure;
+ HR(this->createXpsRect(universeRect, FALSE, TRUE, &shadedFigure));
+ HRM(shadedFigures->Append(shadedFigure.get()),
+ "Could not add inverse shaded figure.");
+ //the geometry is already set
+ HR(this->clip(shadedPath, d));
+ HRM(newVisuals->Append(shadedPath),
+ "Could not add inverse shaded path to canvas visuals.");
+
+ //Draw the old canvas on top, clipped to the original path.
+ SkTScopedComPtr<IXpsOMCanvas> topCanvas;
+ HRM(this->fXpsFactory->CreateCanvas(&topCanvas),
+ "Could not create top inverse canvas.");
+ //Clip the canvas to prevent alpha spill.
+ //This is the entire reason this canvas exists.
+ HR(this->clip(topCanvas.get(), d));
+
+ SkTScopedComPtr<IXpsOMGeometry> topGeometry;
+ HRM(this->fXpsFactory->CreateGeometry(&topGeometry),
+ "Could not create top inverse geometry.");
+
+ SkTScopedComPtr<IXpsOMGeometryFigureCollection> topFigures;
+ HRM(topGeometry->GetFigures(&topFigures),
+ "Could not get top inverse figures.");
+
+ SkTScopedComPtr<IXpsOMGeometryFigure> topFigure;
+ HR(this->createXpsRect(universeRect, FALSE, TRUE, &topFigure));
+ HRM(topFigures->Append(topFigure.get()),
+ "Could not add old inverse figure.");
+
+ SkTScopedComPtr<IXpsOMVisualBrush> topBrush;
+ HRM(this->fXpsFactory->CreateVisualBrush(&universeRectXps,
+ &universeRectXps,
+ &topBrush),
+ "Could not create top inverse brush.");
+
+ SkTScopedComPtr<IXpsOMPath> topPath;
+ HRM(this->fXpsFactory->CreatePath(&topPath),
+ "Could not create top inverse path.");
+ HRM(topPath->SetGeometryLocal(topGeometry.get()),
+ "Could not set top inverse geometry.");
+ HRM(topPath->SetFillBrushLocal(topBrush.get()),
+ "Could not set top inverse fill brush.");
+ //the brush must be parented before setting the lookup.
+ HRM(newVisuals->Append(topCanvas.get()),
+ "Could not add top canvas to inverse canvas visuals.");
+ SkTScopedComPtr<IXpsOMVisualCollection> topVisuals;
+ HRM(topCanvas->GetVisuals(&topVisuals),
+ "Could not get top inverse canvas visuals.");
+ HRM(topVisuals->Append(topPath.get()),
+ "Could not add top inverse path to top canvas visuals.");
+ HRM(topBrush->SetVisualLookup(buffer),
+ "Could not set top inverse brush visual lookup.");
+
+ HR(this->clipToPath(topPath.get(), devicePath, XPS_FILL_RULE_NONZERO));
+
+ //swap current canvas to new canvas
+ this->fCurrentXpsCanvas.swap(newCanvas);
+
+ return S_OK;
+}
+
+void SkXPSDevice::convertToPpm(const SkMaskFilter* filter,
+ SkMatrix* matrix,
+ SkVector* ppuScale,
+ const SkIRect& clip, SkIRect* clipIRect) {
+ //TODO: currently ignoring the ppm if blur ignoring transform.
+ if (filter) {
+ SkMaskFilter::BlurInfo blurInfo;
+ SkMaskFilter::BlurType blurType = filter->asABlur(&blurInfo);
+
+ if (SkMaskFilter::kNone_BlurType != blurType
+ && blurInfo.fIgnoreTransform) {
+
+ ppuScale->fX = SK_Scalar1;
+ ppuScale->fY = SK_Scalar1;
+ *clipIRect = clip;
+ return;
+ }
+ }
+
+ //This action is in unit space, but the ppm is specified in physical space.
+ ppuScale->fX = SkScalarDiv(this->fCurrentPixelsPerMeter.fX,
+ this->fCurrentUnitsPerMeter.fX);
+ ppuScale->fY = SkScalarDiv(this->fCurrentPixelsPerMeter.fY,
+ this->fCurrentUnitsPerMeter.fY);
+
+ matrix->postScale(ppuScale->fX, ppuScale->fY);
+
+ const SkIRect& irect = clip;
+ SkRect clipRect = SkRect::MakeLTRB(
+ SkScalarMul(SkIntToScalar(irect.fLeft), ppuScale->fX),
+ SkScalarMul(SkIntToScalar(irect.fTop), ppuScale->fY),
+ SkScalarMul(SkIntToScalar(irect.fRight), ppuScale->fX),
+ SkScalarMul(SkIntToScalar(irect.fBottom), ppuScale->fY));
+ clipRect.roundOut(clipIRect);
+}
+
+HRESULT SkXPSDevice::applyMask(const SkDraw& d,
+ const SkMask& mask,
+ const SkVector& ppuScale,
+ IXpsOMPath* shadedPath) {
+ //Get the geometry object.
+ SkTScopedComPtr<IXpsOMGeometry> shadedGeometry;
+ HRM(shadedPath->GetGeometry(&shadedGeometry),
+ "Could not get mask shaded geometry.");
+
+ //Get the figures from the geometry.
+ SkTScopedComPtr<IXpsOMGeometryFigureCollection> shadedFigures;
+ HRM(shadedGeometry->GetFigures(&shadedFigures),
+ "Could not get mask shaded figures.");
+
+ SkMatrix m;
+ m.reset();
+ m.setTranslate(SkIntToScalar(mask.fBounds.fLeft),
+ SkIntToScalar(mask.fBounds.fTop));
+ m.postScale(SkScalarInvert(ppuScale.fX), SkScalarInvert(ppuScale.fY));
+
+ SkShader::TileMode xy[2];
+ xy[0] = (SkShader::TileMode)3;
+ xy[1] = (SkShader::TileMode)3;
+
+ SkBitmap bm;
+ bm.setConfig(SkBitmap::kA8_Config,
+ mask.fBounds.width(),
+ mask.fBounds.height(),
+ mask.fRowBytes);
+ bm.setPixels(mask.fImage);
+
+ SkTScopedComPtr<IXpsOMTileBrush> maskBrush;
+ HR(this->createXpsImageBrush(bm, m, xy, 0xFF, &maskBrush));
+ HRM(shadedPath->SetOpacityMaskBrushLocal(maskBrush.get()),
+ "Could not set mask.");
+
+ const SkRect universeRect = SkRect::MakeLTRB(0, 0,
+ this->fCurrentCanvasSize.fWidth, this->fCurrentCanvasSize.fHeight);
+ SkTScopedComPtr<IXpsOMGeometryFigure> shadedFigure;
+ HRM(this->createXpsRect(universeRect, FALSE, TRUE, &shadedFigure),
+ "Could not create mask shaded figure.");
+ HRM(shadedFigures->Append(shadedFigure.get()),
+ "Could not add mask shaded figure.");
+
+ HR(this->clip(shadedPath, d));
+
+ //Add the path to the active visual collection.
+ SkTScopedComPtr<IXpsOMVisualCollection> currentVisuals;
+ HRM(this->fCurrentXpsCanvas->GetVisuals(&currentVisuals),
+ "Could not get mask current visuals.");
+ HRM(currentVisuals->Append(shadedPath),
+ "Could not add masked shaded path to current visuals.");
+
+ return S_OK;
+}
+
+HRESULT SkXPSDevice::shadePath(IXpsOMPath* shadedPath,
+ const SkPaint& shaderPaint,
+ const SkMatrix& matrix,
+ BOOL* fill, BOOL* stroke) {
+ *fill = FALSE;
+ *stroke = FALSE;
+
+ const SkPaint::Style style = shaderPaint.getStyle();
+ const bool hasFill = SkPaint::kFill_Style == style
+ || SkPaint::kStrokeAndFill_Style == style;
+ const bool hasStroke = SkPaint::kStroke_Style == style
+ || SkPaint::kStrokeAndFill_Style == style;
+
+ //TODO(bungeman): use dictionaries and lookups.
+ if (hasFill) {
+ *fill = TRUE;
+ SkTScopedComPtr<IXpsOMBrush> fillBrush;
+ HR(this->createXpsBrush(shaderPaint, &fillBrush, &matrix));
+ HRM(shadedPath->SetFillBrushLocal(fillBrush.get()),
+ "Could not set fill for shaded path.");
+ }
+
+ if (hasStroke) {
+ *stroke = TRUE;
+ SkTScopedComPtr<IXpsOMBrush> strokeBrush;
+ HR(this->createXpsBrush(shaderPaint, &strokeBrush, &matrix));
+ HRM(shadedPath->SetStrokeBrushLocal(strokeBrush.get()),
+ "Could not set stroke brush for shaded path.");
+ HRM(shadedPath->SetStrokeThickness(
+ SkScalarToFLOAT(shaderPaint.getStrokeWidth())),
+ "Could not set shaded path stroke thickness.");
+
+ if (0 == shaderPaint.getStrokeWidth()) {
+ //XPS hair width is a hack. (XPS Spec 11.6.12).
+ SkTScopedComPtr<IXpsOMDashCollection> dashes;
+ HRM(shadedPath->GetStrokeDashes(&dashes),
+ "Could not set dashes for shaded path.");
+ XPS_DASH dash;
+ dash.length = 1.0;
+ dash.gap = 0.0;
+ HRM(dashes->Append(&dash), "Could not add dashes to shaded path.");
+ HRM(shadedPath->SetStrokeDashOffset(-2.0),
+ "Could not set dash offset for shaded path.");
+ }
+ }
+ return S_OK;
+}
+
+void SkXPSDevice::drawPath(const SkDraw& d,
+ const SkPath& platonicPath,
+ const SkPaint& origPaint,
+ const SkMatrix* prePathMatrix,
+ bool pathIsMutable) {
+ SkTCopyOnFirstWrite<SkPaint> paint(origPaint);
+
+ // nothing to draw
+ if (d.fClip->isEmpty() ||
+ (paint->getAlpha() == 0 && paint->getXfermode() == NULL)) {
+ return;
+ }
+
+ SkPath modifiedPath;
+ const bool paintHasPathEffect = paint->getPathEffect()
+ || paint->getStyle() != SkPaint::kFill_Style;
+
+ //Apply pre-path matrix [Platonic-path -> Skeletal-path].
+ SkMatrix matrix = *d.fMatrix;
+ SkPath* skeletalPath = const_cast<SkPath*>(&platonicPath);
+ if (prePathMatrix) {
+ if (paintHasPathEffect || paint->getRasterizer()) {
+ if (!pathIsMutable) {
+ skeletalPath = &modifiedPath;
+ pathIsMutable = true;
+ }
+ platonicPath.transform(*prePathMatrix, skeletalPath);
+ } else {
+ if (!matrix.preConcat(*prePathMatrix)) {
+ return;
+ }
+ }
+ }
+
+ //Apply path effect [Skeletal-path -> Fillable-path].
+ SkPath* fillablePath = skeletalPath;
+ if (paintHasPathEffect) {
+ if (!pathIsMutable) {
+ fillablePath = &modifiedPath;
+ pathIsMutable = true;
+ }
+ bool fill = paint->getFillPath(*skeletalPath, fillablePath);
+
+ SkPaint* writablePaint = paint.writable();
+ writablePaint->setPathEffect(NULL);
+ if (fill) {
+ writablePaint->setStyle(SkPaint::kFill_Style);
+ } else {
+ writablePaint->setStyle(SkPaint::kStroke_Style);
+ writablePaint->setStrokeWidth(0);
+ }
+ }
+
+ //Create the shaded path. This will be the path which is painted.
+ SkTScopedComPtr<IXpsOMPath> shadedPath;
+ HRVM(this->fXpsFactory->CreatePath(&shadedPath),
+ "Could not create shaded path for path.");
+
+ //Create the geometry for the shaded path.
+ SkTScopedComPtr<IXpsOMGeometry> shadedGeometry;
+ HRVM(this->fXpsFactory->CreateGeometry(&shadedGeometry),
+ "Could not create shaded geometry for path.");
+
+ //Add the geometry to the shaded path.
+ HRVM(shadedPath->SetGeometryLocal(shadedGeometry.get()),
+ "Could not add the shaded geometry to shaded path.");
+
+ SkRasterizer* rasterizer = paint->getRasterizer();
+ SkMaskFilter* filter = paint->getMaskFilter();
+
+ //Determine if we will draw or shade and mask.
+ if (rasterizer || filter) {
+ if (paint->getStyle() != SkPaint::kFill_Style) {
+ paint.writable()->setStyle(SkPaint::kFill_Style);
+ }
+ }
+
+ //Set the brushes.
+ BOOL fill;
+ BOOL stroke;
+ HRV(this->shadePath(shadedPath.get(),
+ *paint,
+ *d.fMatrix,
+ &fill,
+ &stroke));
+
+ //Rasterizer
+ if (rasterizer) {
+ SkIRect clipIRect;
+ SkVector ppuScale;
+ this->convertToPpm(filter,
+ &matrix,
+ &ppuScale,
+ d.fClip->getBounds(),
+ &clipIRect);
+
+ SkMask* mask = NULL;
+
+ //[Fillable-path -> Mask]
+ SkMask rasteredMask;
+ if (rasterizer->rasterize(
+ *fillablePath,
+ matrix,
+ &clipIRect,
+ filter, //just to compute how much to draw.
+ &rasteredMask,
+ SkMask::kComputeBoundsAndRenderImage_CreateMode)) {
+
+ SkAutoMaskFreeImage rasteredAmi(rasteredMask.fImage);
+ mask = &rasteredMask;
+
+ //[Mask -> Mask]
+ SkMask filteredMask;
+ if (filter &&
+ filter->filterMask(&filteredMask, *mask, *d.fMatrix, NULL)) {
+
+ mask = &filteredMask;
+ } else {
+ filteredMask.fImage = NULL;
+ }
+ SkAutoMaskFreeImage filteredAmi(filteredMask.fImage);
+
+ //Draw mask.
+ HRV(this->applyMask(d, *mask, ppuScale, shadedPath.get()));
+ }
+ return;
+ }
+
+ //Mask filter
+ if (filter) {
+ SkIRect clipIRect;
+ SkVector ppuScale;
+ this->convertToPpm(filter,
+ &matrix,
+ &ppuScale,
+ d.fClip->getBounds(),
+ &clipIRect);
+
+ //[Fillable-path -> Pixel-path]
+ SkPath* pixelPath = pathIsMutable ? fillablePath : &modifiedPath;
+ fillablePath->transform(matrix, pixelPath);
+
+ SkMask* mask = NULL;
+
+ //[Pixel-path -> Mask]
+ SkMask rasteredMask;
+ if (SkDraw::DrawToMask(
+ *pixelPath,
+ &clipIRect,
+ filter, //just to compute how much to draw.
+ &matrix,
+ &rasteredMask,
+ SkMask::kComputeBoundsAndRenderImage_CreateMode,
+ paint->getStyle())) {
+
+ SkAutoMaskFreeImage rasteredAmi(rasteredMask.fImage);
+ mask = &rasteredMask;
+
+ //[Mask -> Mask]
+ SkMask filteredMask;
+ if (filter->filterMask(&filteredMask,
+ rasteredMask,
+ matrix,
+ NULL)) {
+ mask = &filteredMask;
+ } else {
+ filteredMask.fImage = NULL;
+ }
+ SkAutoMaskFreeImage filteredAmi(filteredMask.fImage);
+
+ //Draw mask.
+ HRV(this->applyMask(d, *mask, ppuScale, shadedPath.get()));
+ }
+ return;
+ }
+
+ //Get the figures from the shaded geometry.
+ SkTScopedComPtr<IXpsOMGeometryFigureCollection> shadedFigures;
+ HRVM(shadedGeometry->GetFigures(&shadedFigures),
+ "Could not get shaded figures for shaded path.");
+
+ bool xpsTransformsPath = true;
+
+ //Set the fill rule.
+ XPS_FILL_RULE xpsFillRule;
+ switch (platonicPath.getFillType()) {
+ case SkPath::kWinding_FillType:
+ xpsFillRule = XPS_FILL_RULE_NONZERO;
+ break;
+ case SkPath::kEvenOdd_FillType:
+ xpsFillRule = XPS_FILL_RULE_EVENODD;
+ break;
+ case SkPath::kInverseWinding_FillType: {
+ //[Fillable-path -> Device-path]
+ SkPath* devicePath = pathIsMutable ? fillablePath : &modifiedPath;
+ fillablePath->transform(matrix, devicePath);
+
+ HRV(this->drawInverseWindingPath(d,
+ *devicePath,
+ shadedPath.get()));
+ return;
+ }
+ case SkPath::kInverseEvenOdd_FillType: {
+ const SkRect universe = SkRect::MakeLTRB(
+ 0, 0,
+ this->fCurrentCanvasSize.fWidth,
+ this->fCurrentCanvasSize.fHeight);
+ SkTScopedComPtr<IXpsOMGeometryFigure> addOneFigure;
+ HRV(this->createXpsRect(universe, FALSE, TRUE, &addOneFigure));
+ HRVM(shadedFigures->Append(addOneFigure.get()),
+ "Could not add even-odd flip figure to shaded path.");
+ xpsTransformsPath = false;
+ xpsFillRule = XPS_FILL_RULE_EVENODD;
+ break;
+ }
+ default:
+ SkASSERT(!"Unknown SkPath::FillType.");
+ }
+ HRVM(shadedGeometry->SetFillRule(xpsFillRule),
+ "Could not set fill rule for shaded path.");
+
+ //Create the XPS transform, if possible.
+ if (xpsTransformsPath) {
+ SkTScopedComPtr<IXpsOMMatrixTransform> xpsTransform;
+ HRV(this->createXpsTransform(matrix, &xpsTransform));
+
+ if (xpsTransform.get()) {
+ HRVM(shadedGeometry->SetTransformLocal(xpsTransform.get()),
+ "Could not set transform on shaded path.");
+ } else {
+ xpsTransformsPath = false;
+ }
+ }
+
+ SkPath* devicePath = fillablePath;
+ if (!xpsTransformsPath) {
+ //[Fillable-path -> Device-path]
+ devicePath = pathIsMutable ? fillablePath : &modifiedPath;
+ fillablePath->transform(matrix, devicePath);
+ }
+ HRV(this->addXpsPathGeometry(shadedFigures.get(),
+ stroke, fill, *devicePath));
+
+ HRV(this->clip(shadedPath.get(), d));
+
+ //Add the path to the active visual collection.
+ SkTScopedComPtr<IXpsOMVisualCollection> currentVisuals;
+ HRVM(this->fCurrentXpsCanvas->GetVisuals(&currentVisuals),
+ "Could not get current visuals for shaded path.");
+ HRVM(currentVisuals->Append(shadedPath.get()),
+ "Could not add shaded path to current visuals.");
+}
+
+HRESULT SkXPSDevice::clip(IXpsOMVisual* xpsVisual, const SkDraw& d) {
+ SkPath clipPath;
+ SkAssertResult(d.fClip->getBoundaryPath(&clipPath));
+
+ return this->clipToPath(xpsVisual, clipPath, XPS_FILL_RULE_EVENODD);
+}
+HRESULT SkXPSDevice::clipToPath(IXpsOMVisual* xpsVisual,
+ const SkPath& clipPath,
+ XPS_FILL_RULE fillRule) {
+ //Create the geometry.
+ SkTScopedComPtr<IXpsOMGeometry> clipGeometry;
+ HRM(this->fXpsFactory->CreateGeometry(&clipGeometry),
+ "Could not create clip geometry.");
+
+ //Get the figure collection of the geometry.
+ SkTScopedComPtr<IXpsOMGeometryFigureCollection> clipFigures;
+ HRM(clipGeometry->GetFigures(&clipFigures),
+ "Could not get the clip figures.");
+
+ //Create the figures into the geometry.
+ HR(this->addXpsPathGeometry(
+ clipFigures.get(),
+ FALSE, TRUE, clipPath));
+
+ HRM(clipGeometry->SetFillRule(fillRule),
+ "Could not set fill rule.");
+ HRM(xpsVisual->SetClipGeometryLocal(clipGeometry.get()),
+ "Could not set clip geometry.");
+
+ return S_OK;
+}
+
+void SkXPSDevice::drawBitmap(const SkDraw& d, const SkBitmap& bitmap,
+ const SkIRect* srcRectOrNull,
+ const SkMatrix& matrix, const SkPaint& paint) {
+ if (d.fClip->isEmpty()) {
+ return;
+ }
+
+ SkIRect srcRect;
+ SkBitmap tmp;
+ const SkBitmap* bitmapPtr = &bitmap;
+ if (NULL == srcRectOrNull) {
+ srcRect.set(0, 0, bitmap.width(), bitmap.height());
+ bitmapPtr = &bitmap;
+ } else {
+ srcRect = *srcRectOrNull;
+ if (!bitmap.extractSubset(&tmp, srcRect)) {
+ return; // extraction failed
+ }
+ bitmapPtr = &tmp;
+ }
+
+ //Create the new shaded path.
+ SkTScopedComPtr<IXpsOMPath> shadedPath;
+ HRVM(this->fXpsFactory->CreatePath(&shadedPath),
+ "Could not create path for bitmap.");
+
+ //Create the shaded geometry.
+ SkTScopedComPtr<IXpsOMGeometry> shadedGeometry;
+ HRVM(this->fXpsFactory->CreateGeometry(&shadedGeometry),
+ "Could not create geometry for bitmap.");
+
+ //Add the shaded geometry to the shaded path.
+ HRVM(shadedPath->SetGeometryLocal(shadedGeometry.get()),
+ "Could not set the geometry for bitmap.");
+
+ //Get the shaded figures from the shaded geometry.
+ SkTScopedComPtr<IXpsOMGeometryFigureCollection> shadedFigures;
+ HRVM(shadedGeometry->GetFigures(&shadedFigures),
+ "Could not get the figures for bitmap.");
+
+ SkMatrix transform = matrix;
+ transform.postConcat(*d.fMatrix);
+
+ SkTScopedComPtr<IXpsOMMatrixTransform> xpsTransform;
+ HRV(this->createXpsTransform(transform, &xpsTransform));
+ if (xpsTransform.get()) {
+ HRVM(shadedGeometry->SetTransformLocal(xpsTransform.get()),
+ "Could not set transform for bitmap.");
+ } else {
+ //TODO: perspective that bitmap!
+ }
+
+ SkTScopedComPtr<IXpsOMGeometryFigure> rectFigure;
+ if (NULL != xpsTransform.get()) {
+ const SkShader::TileMode xy[2] = {
+ SkShader::kClamp_TileMode,
+ SkShader::kClamp_TileMode,
+ };
+ SkTScopedComPtr<IXpsOMTileBrush> xpsImageBrush;
+ HRV(this->createXpsImageBrush(*bitmapPtr,
+ transform,
+ xy,
+ paint.getAlpha(),
+ &xpsImageBrush));
+ HRVM(shadedPath->SetFillBrushLocal(xpsImageBrush.get()),
+ "Could not set bitmap brush.");
+
+ const SkRect bitmapRect = SkRect::MakeLTRB(0, 0,
+ SkIntToScalar(srcRect.width()), SkIntToScalar(srcRect.height()));
+ HRV(this->createXpsRect(bitmapRect, FALSE, TRUE, &rectFigure));
+ }
+ HRVM(shadedFigures->Append(rectFigure.get()),
+ "Could not add bitmap figure.");
+
+ //Get the current visual collection and add the shaded path to it.
+ SkTScopedComPtr<IXpsOMVisualCollection> currentVisuals;
+ HRVM(this->fCurrentXpsCanvas->GetVisuals(&currentVisuals),
+ "Could not get current visuals for bitmap");
+ HRVM(currentVisuals->Append(shadedPath.get()),
+ "Could not add bitmap to current visuals.");
+
+ HRV(this->clip(shadedPath.get(), d));
+}
+
+void SkXPSDevice::drawSprite(const SkDraw&, const SkBitmap& bitmap,
+ int x, int y,
+ const SkPaint& paint) {
+ //TODO: override this for XPS
+ SkDEBUGF(("XPS drawSprite not yet implemented."));
+}
+
+HRESULT SkXPSDevice::CreateTypefaceUse(const SkPaint& paint,
+ TypefaceUse** typefaceUse) {
+ const SkTypeface* typeface = paint.getTypeface();
+
+ //Check cache.
+ const SkFontID typefaceID = SkTypeface::UniqueID(typeface);
+ if (!this->fTypefaces.empty()) {
+ TypefaceUse* current = &this->fTypefaces.front();
+ const TypefaceUse* last = &this->fTypefaces.back();
+ for (; current <= last; ++current) {
+ if (current->typefaceId == typefaceID) {
+ *typefaceUse = current;
+ return S_OK;
+ }
+ }
+ }
+
+ //TODO: create glyph only fonts
+ //and let the host deal with what kind of font we're looking at.
+ XPS_FONT_EMBEDDING embedding = XPS_FONT_EMBEDDING_RESTRICTED;
+
+ SkTScopedComPtr<IStream> fontStream;
+ SkStream* fontData = SkFontHost::OpenStream(typefaceID);
+ HRM(SkIStream::CreateFromSkStream(fontData, true, &fontStream),
+ "Could not create font stream.");
+
+ const size_t size =
+ SK_ARRAY_COUNT(L"/Resources/Fonts/" L_GUID_ID L".odttf");
+ wchar_t buffer[size];
+ wchar_t id[GUID_ID_LEN];
+ HR(create_id(id, GUID_ID_LEN));
+ swprintf_s(buffer, size, L"/Resources/Fonts/%s.odttf", id);
+
+ SkTScopedComPtr<IOpcPartUri> partUri;
+ HRM(this->fXpsFactory->CreatePartUri(buffer, &partUri),
+ "Could not create font resource part uri.");
+
+ SkTScopedComPtr<IXpsOMFontResource> xpsFontResource;
+ HRM(this->fXpsFactory->CreateFontResource(fontStream.get(),
+ embedding,
+ partUri.get(),
+ FALSE,
+ &xpsFontResource),
+ "Could not create font resource.");
+
+ TypefaceUse& newTypefaceUse = this->fTypefaces.push_back();
+ newTypefaceUse.typefaceId = typefaceID;
+ newTypefaceUse.fontData = fontData;
+ newTypefaceUse.xpsFont = xpsFontResource.release();
+
+ SkAutoGlyphCache agc = SkAutoGlyphCache(paint, &SkMatrix::I());
+ SkGlyphCache* glyphCache = agc.getCache();
+ unsigned int glyphCount = glyphCache->getGlyphCount();
+ newTypefaceUse.glyphsUsed = new SkBitSet(glyphCount);
+
+ *typefaceUse = &newTypefaceUse;
+ return S_OK;
+}
+
+HRESULT SkXPSDevice::AddGlyphs(const SkDraw& d,
+ IXpsOMObjectFactory* xpsFactory,
+ IXpsOMCanvas* canvas,
+ IXpsOMFontResource* font,
+ LPCWSTR text,
+ XPS_GLYPH_INDEX* xpsGlyphs,
+ UINT32 xpsGlyphsLen,
+ XPS_POINT *origin,
+ FLOAT fontSize,
+ XPS_STYLE_SIMULATION sims,
+ const SkMatrix& transform,
+ const SkPaint& paint) {
+ SkTScopedComPtr<IXpsOMGlyphs> glyphs;
+ HRM(xpsFactory->CreateGlyphs(font, &glyphs), "Could not create glyphs.");
+
+ //XPS uses affine transformations for everything...
+ //...except positioning text.
+ bool useCanvasForClip;
+ if ((transform.getType() & ~SkMatrix::kTranslate_Mask) == 0) {
+ origin->x += SkScalarToFLOAT(transform.getTranslateX());
+ origin->y += SkScalarToFLOAT(transform.getTranslateY());
+ useCanvasForClip = false;
+ } else {
+ SkTScopedComPtr<IXpsOMMatrixTransform> xpsMatrixToUse;
+ HR(this->createXpsTransform(transform, &xpsMatrixToUse));
+ if (xpsMatrixToUse.get()) {
+ HRM(glyphs->SetTransformLocal(xpsMatrixToUse.get()),
+ "Could not set transform matrix.");
+ useCanvasForClip = true;
+ } else {
+ SkASSERT(!"Attempt to add glyphs in perspective.");
+ useCanvasForClip = false;
+ }
+ }
+
+ SkTScopedComPtr<IXpsOMGlyphsEditor> glyphsEditor;
+ HRM(glyphs->GetGlyphsEditor(&glyphsEditor), "Could not get glyph editor.");
+
+ if (NULL != text) {
+ HRM(glyphsEditor->SetUnicodeString(text),
+ "Could not set unicode string.");
+ }
+
+ if (NULL != xpsGlyphs) {
+ HRM(glyphsEditor->SetGlyphIndices(xpsGlyphsLen, xpsGlyphs),
+ "Could not set glyphs.");
+ }
+
+ HRM(glyphsEditor->ApplyEdits(), "Could not apply glyph edits.");
+
+ SkTScopedComPtr<IXpsOMBrush> xpsFillBrush;
+ HR(this->createXpsBrush(
+ paint,
+ &xpsFillBrush,
+ useCanvasForClip ? NULL : &transform));
+
+ HRM(glyphs->SetFillBrushLocal(xpsFillBrush.get()),
+ "Could not set fill brush.");
+
+ HRM(glyphs->SetOrigin(origin), "Could not set glyph origin.");
+
+ HRM(glyphs->SetFontRenderingEmSize(fontSize),
+ "Could not set font size.");
+
+ HRM(glyphs->SetStyleSimulations(sims),
+ "Could not set style simulations.");
+
+ SkTScopedComPtr<IXpsOMVisualCollection> visuals;
+ HRM(canvas->GetVisuals(&visuals), "Could not get glyph canvas visuals.");
+
+ if (!useCanvasForClip) {
+ HR(this->clip(glyphs.get(), d));
+ HRM(visuals->Append(glyphs.get()), "Could not add glyphs to canvas.");
+ } else {
+ SkTScopedComPtr<IXpsOMCanvas> glyphCanvas;
+ HRM(this->fXpsFactory->CreateCanvas(&glyphCanvas),
+ "Could not create glyph canvas.");
+
+ SkTScopedComPtr<IXpsOMVisualCollection> glyphCanvasVisuals;
+ HRM(glyphCanvas->GetVisuals(&glyphCanvasVisuals),
+ "Could not get glyph visuals collection.");
+
+ HRM(glyphCanvasVisuals->Append(glyphs.get()),
+ "Could not add glyphs to page.");
+ HR(this->clip(glyphCanvas.get(), d));
+
+ HRM(visuals->Append(glyphCanvas.get()),
+ "Could not add glyph canvas to page.");
+ }
+
+ return S_OK;
+}
+
+struct SkXPSDrawProcs : public SkDrawProcs {
+public:
+ /** [in] Advance width and offsets for glyphs measured in
+ hundredths of the font em size (XPS Spec 5.1.3). */
+ FLOAT centemPerUnit;
+ /** [in,out] The accumulated glyphs used in the current typeface. */
+ SkBitSet* glyphUse;
+ /** [out] The glyphs to draw. */
+ SkTDArray<XPS_GLYPH_INDEX> xpsGlyphs;
+};
+
+static void xps_draw_1_glyph(const SkDraw1Glyph& state,
+ SkFixed x, SkFixed y,
+ const SkGlyph& skGlyph) {
+ SkASSERT(skGlyph.fWidth > 0 && skGlyph.fHeight > 0);
+
+ SkXPSDrawProcs* procs = static_cast<SkXPSDrawProcs*>(state.fDraw->fProcs);
+
+ //Draw pre-adds half the sampling frequency for floor rounding.
+ if (state.fCache->isSubpixel()) {
+ x -= (SK_FixedHalf >> SkGlyph::kSubBits);
+ y -= (SK_FixedHalf >> SkGlyph::kSubBits);
+ } else {
+ x -= SK_FixedHalf;
+ y -= SK_FixedHalf;
+ }
+
+ XPS_GLYPH_INDEX* xpsGlyph = procs->xpsGlyphs.append();
+ uint16_t glyphID = skGlyph.getGlyphID();
+ procs->glyphUse->setBit(glyphID, true);
+ xpsGlyph->index = glyphID;
+ if (1 == procs->xpsGlyphs.count()) {
+ xpsGlyph->advanceWidth = 0.0f;
+ xpsGlyph->horizontalOffset = SkFixedToFloat(x) * procs->centemPerUnit;
+ xpsGlyph->verticalOffset = SkFixedToFloat(y) * -procs->centemPerUnit;
+ } else {
+ const XPS_GLYPH_INDEX& first = procs->xpsGlyphs[0];
+ xpsGlyph->advanceWidth = 0.0f;
+ xpsGlyph->horizontalOffset = (SkFixedToFloat(x) * procs->centemPerUnit)
+ - first.horizontalOffset;
+ xpsGlyph->verticalOffset = (SkFixedToFloat(y) * -procs->centemPerUnit)
+ - first.verticalOffset;
+ }
+}
+
+static void text_draw_init(const SkPaint& paint,
+ const void* text, size_t byteLength,
+ SkBitSet& glyphsUsed,
+ SkDraw& myDraw, SkXPSDrawProcs& procs) {
+ procs.fD1GProc = xps_draw_1_glyph;
+ int numGlyphGuess;
+ switch (paint.getTextEncoding()) {
+ case SkPaint::kUTF8_TextEncoding:
+ numGlyphGuess = SkUTF8_CountUnichars(
+ static_cast<const char *>(text),
+ byteLength);
+ break;
+ case SkPaint::kUTF16_TextEncoding:
+ numGlyphGuess = SkUTF16_CountUnichars(
+ static_cast<const uint16_t *>(text),
+ byteLength);
+ break;
+ case SkPaint::kGlyphID_TextEncoding:
+ numGlyphGuess = byteLength / 2;
+ break;
+ default:
+ SK_DEBUGBREAK(true);
+ }
+ procs.xpsGlyphs.setReserve(numGlyphGuess);
+ procs.glyphUse = &glyphsUsed;
+ procs.centemPerUnit = 100.0f / SkScalarToFLOAT(paint.getTextSize());
+
+ myDraw.fProcs = &procs;
+}
+
+static bool text_must_be_pathed(const SkPaint& paint, const SkMatrix& matrix) {
+ const SkPaint::Style style = paint.getStyle();
+ return matrix.hasPerspective()
+ || SkPaint::kStroke_Style == style
+ || SkPaint::kStrokeAndFill_Style == style
+ || paint.getMaskFilter()
+ || paint.getRasterizer()
+ ;
+}
+
+void SkXPSDevice::drawText(const SkDraw& d,
+ const void* text, size_t byteLen,
+ SkScalar x, SkScalar y,
+ const SkPaint& paint) {
+ if (byteLen < 1) return;
+
+ if (text_must_be_pathed(paint, *d.fMatrix)) {
+ SkPath path;
+ paint.getTextPath(text, byteLen, x, y, &path);
+ this->drawPath(d, path, paint, NULL, true);
+ //TODO: add automation "text"
+ return;
+ }
+
+ TypefaceUse* typeface;
+ HRV(CreateTypefaceUse(paint, &typeface));
+
+ SkDraw myDraw(d);
+ SkXPSDrawProcs procs;
+ text_draw_init(paint, text, byteLen, *typeface->glyphsUsed, myDraw, procs);
+
+ myDraw.drawText(static_cast<const char*>(text), byteLen, x, y, paint);
+
+ // SkDraw may have clipped out the glyphs, so we need to check
+ if (procs.xpsGlyphs.count() == 0) {
+ return;
+ }
+
+ XPS_POINT origin = {
+ procs.xpsGlyphs[0].horizontalOffset / procs.centemPerUnit,
+ procs.xpsGlyphs[0].verticalOffset / -procs.centemPerUnit,
+ };
+ procs.xpsGlyphs[0].horizontalOffset = 0.0f;
+ procs.xpsGlyphs[0].verticalOffset = 0.0f;
+
+ HRV(AddGlyphs(d,
+ this->fXpsFactory.get(),
+ this->fCurrentXpsCanvas.get(),
+ typeface->xpsFont,
+ NULL,
+ procs.xpsGlyphs.begin(), procs.xpsGlyphs.count(),
+ &origin,
+ SkScalarToFLOAT(paint.getTextSize()),
+ XPS_STYLE_SIMULATION_NONE,
+ *d.fMatrix,
+ paint));
+}
+
+void SkXPSDevice::drawPosText(const SkDraw& d,
+ const void* text, size_t byteLen,
+ const SkScalar pos[],
+ SkScalar constY, int scalarsPerPos,
+ const SkPaint& paint) {
+ if (byteLen < 1) return;
+
+ if (text_must_be_pathed(paint, *d.fMatrix)) {
+ SkPath path;
+ //TODO: make this work, Draw currently does not handle as well.
+ //paint.getTextPath(text, byteLength, x, y, &path);
+ //this->drawPath(d, path, paint, NULL, true);
+ //TODO: add automation "text"
+ return;
+ }
+
+ TypefaceUse* typeface;
+ HRV(CreateTypefaceUse(paint, &typeface));
+
+ SkDraw myDraw(d);
+ SkXPSDrawProcs procs;
+ text_draw_init(paint, text, byteLen, *typeface->glyphsUsed, myDraw, procs);
+
+ myDraw.drawPosText(static_cast<const char*>(text), byteLen,
+ pos, constY, scalarsPerPos,
+ paint);
+
+ // SkDraw may have clipped out the glyphs, so we need to check
+ if (procs.xpsGlyphs.count() == 0) {
+ return;
+ }
+
+ XPS_POINT origin = {
+ procs.xpsGlyphs[0].horizontalOffset / procs.centemPerUnit,
+ procs.xpsGlyphs[0].verticalOffset / -procs.centemPerUnit,
+ };
+ procs.xpsGlyphs[0].horizontalOffset = 0.0f;
+ procs.xpsGlyphs[0].verticalOffset = 0.0f;
+
+ HRV(AddGlyphs(d,
+ this->fXpsFactory.get(),
+ this->fCurrentXpsCanvas.get(),
+ typeface->xpsFont,
+ NULL,
+ procs.xpsGlyphs.begin(), procs.xpsGlyphs.count(),
+ &origin,
+ SkScalarToFLOAT(paint.getTextSize()),
+ XPS_STYLE_SIMULATION_NONE,
+ *d.fMatrix,
+ paint));
+}
+
+void SkXPSDevice::drawTextOnPath(const SkDraw& d, const void* text, size_t len,
+ const SkPath& path, const SkMatrix* matrix,
+ const SkPaint& paint) {
+ //This will call back into the device to do the drawing.
+ d.drawTextOnPath((const char*)text, len, path, matrix, paint);
+}
+
+void SkXPSDevice::drawDevice(const SkDraw& d, SkDevice* dev,
+ int x, int y,
+ const SkPaint&) {
+ SkXPSDevice* that = static_cast<SkXPSDevice*>(dev);
+
+ SkTScopedComPtr<IXpsOMMatrixTransform> xpsTransform;
+ XPS_MATRIX rawTransform = {
+ 1.0f,
+ 0.0f,
+ 0.0f,
+ 1.0f,
+ static_cast<FLOAT>(x),
+ static_cast<FLOAT>(y),
+ };
+ HRVM(this->fXpsFactory->CreateMatrixTransform(&rawTransform, &xpsTransform),
+ "Could not create layer transform.");
+ HRVM(that->fCurrentXpsCanvas->SetTransformLocal(xpsTransform.get()),
+ "Could not set layer transform.");
+
+ //Get the current visual collection and add the layer to it.
+ SkTScopedComPtr<IXpsOMVisualCollection> currentVisuals;
+ HRVM(this->fCurrentXpsCanvas->GetVisuals(&currentVisuals),
+ "Could not get current visuals for layer.");
+ HRVM(currentVisuals->Append(that->fCurrentXpsCanvas.get()),
+ "Could not add layer to current visuals.");
+}
+
+bool SkXPSDevice::onReadPixels(const SkBitmap& bitmap, int x, int y,
+ SkCanvas::Config8888) {
+ return false;
+}
+
+SkDevice* SkXPSDevice::onCreateCompatibleDevice(SkBitmap::Config config,
+ int width, int height,
+ bool isOpaque,
+ Usage usage) {
+ if (SkDevice::kGeneral_Usage == usage) {
+ return NULL;
+ SK_CRASH();
+ //To what stream do we write?
+ //SkXPSDevice* dev = new SkXPSDevice(this);
+ //SkSize s = SkSize::Make(width, height);
+ //dev->BeginCanvas(s, s, SkMatrix::I());
+ //return dev;
+ }
+
+ return new SkXPSDevice(this->fXpsFactory.get());
+}
+
+SkXPSDevice::SkXPSDevice(IXpsOMObjectFactory* xpsFactory)
+ : SkDevice(make_fake_bitmap(10000, 10000))
+ , fCurrentPage(0) {
+
+ HRVM(CoCreateInstance(
+ CLSID_XpsOMObjectFactory,
+ NULL,
+ CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(&this->fXpsFactory)),
+ "Could not create factory for layer.");
+
+ HRVM(this->fXpsFactory->CreateCanvas(&this->fCurrentXpsCanvas),
+ "Could not create canvas for layer.");
+}
+
+bool SkXPSDevice::allowImageFilter(SkImageFilter*) {
+ return false;
+}
+
diff --git a/src/effects/Sk1DPathEffect.cpp b/src/effects/Sk1DPathEffect.cpp
index 98345d0f09..3ef050cb88 100644
--- a/src/effects/Sk1DPathEffect.cpp
+++ b/src/effects/Sk1DPathEffect.cpp
@@ -11,7 +11,8 @@
#include "SkFlattenableBuffers.h"
#include "SkPathMeasure.h"
-bool Sk1DPathEffect::filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*) {
+bool Sk1DPathEffect::filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec*) const {
SkPathMeasure meas(src, false);
do {
SkScalar length = meas.getLength();
@@ -68,7 +69,7 @@ SkPath1DPathEffect::SkPath1DPathEffect(const SkPath& path, SkScalar advance,
}
bool SkPath1DPathEffect::filterPath(SkPath* dst, const SkPath& src,
- SkStrokeRec* rec) {
+ SkStrokeRec* rec) const {
if (fAdvance > 0) {
rec->setFillStyle();
return this->INHERITED::filterPath(dst, src, rec);
@@ -159,7 +160,7 @@ SkPath1DPathEffect::SkPath1DPathEffect(SkFlattenableReadBuffer& buffer) {
}
}
-SkScalar SkPath1DPathEffect::begin(SkScalar contourLength) {
+SkScalar SkPath1DPathEffect::begin(SkScalar contourLength) const {
return fInitialOffset;
}
@@ -174,7 +175,7 @@ void SkPath1DPathEffect::flatten(SkFlattenableWriteBuffer& buffer) const {
}
SkScalar SkPath1DPathEffect::next(SkPath* dst, SkScalar distance,
- SkPathMeasure& meas) {
+ SkPathMeasure& meas) const {
switch (fStyle) {
case kTranslate_Style: {
SkPoint pos;
diff --git a/src/effects/Sk2DPathEffect.cpp b/src/effects/Sk2DPathEffect.cpp
index e4d782173a..a4ebe7454f 100644
--- a/src/effects/Sk2DPathEffect.cpp
+++ b/src/effects/Sk2DPathEffect.cpp
@@ -16,7 +16,8 @@ Sk2DPathEffect::Sk2DPathEffect(const SkMatrix& mat) : fMatrix(mat) {
fMatrixIsInvertible = mat.invert(&fInverse);
}
-bool Sk2DPathEffect::filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*) {
+bool Sk2DPathEffect::filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec*) const {
if (!fMatrixIsInvertible) {
return false;
}
@@ -44,7 +45,7 @@ bool Sk2DPathEffect::filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*) {
return true;
}
-void Sk2DPathEffect::nextSpan(int x, int y, int count, SkPath* path) {
+void Sk2DPathEffect::nextSpan(int x, int y, int count, SkPath* path) const {
if (!fMatrixIsInvertible) {
return;
}
@@ -60,9 +61,9 @@ void Sk2DPathEffect::nextSpan(int x, int y, int count, SkPath* path) {
} while (--count > 0);
}
-void Sk2DPathEffect::begin(const SkIRect& uvBounds, SkPath* dst) {}
-void Sk2DPathEffect::next(const SkPoint& loc, int u, int v, SkPath* dst) {}
-void Sk2DPathEffect::end(SkPath* dst) {}
+void Sk2DPathEffect::begin(const SkIRect& uvBounds, SkPath* dst) const {}
+void Sk2DPathEffect::next(const SkPoint& loc, int u, int v, SkPath* dst) const {}
+void Sk2DPathEffect::end(SkPath* dst) const {}
///////////////////////////////////////////////////////////////////////////////
@@ -78,7 +79,8 @@ Sk2DPathEffect::Sk2DPathEffect(SkFlattenableReadBuffer& buffer) {
///////////////////////////////////////////////////////////////////////////////
-bool SkLine2DPathEffect::filterPath(SkPath *dst, const SkPath &src, SkStrokeRec *rec) {
+bool SkLine2DPathEffect::filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec* rec) const {
if (this->INHERITED::filterPath(dst, src, rec)) {
rec->setStrokeStyle(fWidth);
return true;
@@ -86,7 +88,7 @@ bool SkLine2DPathEffect::filterPath(SkPath *dst, const SkPath &src, SkStrokeRec
return false;
}
-void SkLine2DPathEffect::nextSpan(int u, int v, int ucount, SkPath *dst) {
+void SkLine2DPathEffect::nextSpan(int u, int v, int ucount, SkPath* dst) const {
if (ucount > 1) {
SkPoint src[2], dstP[2];
@@ -124,6 +126,7 @@ void SkPath2DPathEffect::flatten(SkFlattenableWriteBuffer& buffer) const {
buffer.writePath(fPath);
}
-void SkPath2DPathEffect::next(const SkPoint& loc, int u, int v, SkPath* dst) {
+void SkPath2DPathEffect::next(const SkPoint& loc, int u, int v,
+ SkPath* dst) const {
dst->addPath(fPath, loc.fX, loc.fY);
}
diff --git a/src/effects/SkArithmeticMode.cpp b/src/effects/SkArithmeticMode.cpp
index c999ce08c9..54a28ce3da 100644
--- a/src/effects/SkArithmeticMode.cpp
+++ b/src/effects/SkArithmeticMode.cpp
@@ -12,7 +12,7 @@ public:
}
virtual void xfer32(SkPMColor dst[], const SkPMColor src[], int count,
- const SkAlpha aa[]) SK_OVERRIDE;
+ const SkAlpha aa[]) const SK_OVERRIDE;
SK_DECLARE_UNFLATTENABLE_OBJECT()
@@ -48,7 +48,7 @@ static bool needsUnpremul(int alpha) {
}
void SkArithmeticMode_scalar::xfer32(SkPMColor dst[], const SkPMColor src[],
- int count, const SkAlpha aaCoverage[]) {
+ int count, const SkAlpha aaCoverage[]) const {
SkScalar k1 = fK[0] / 255;
SkScalar k2 = fK[1];
SkScalar k3 = fK[2];
diff --git a/src/effects/SkAvoidXfermode.cpp b/src/effects/SkAvoidXfermode.cpp
index 89ae1f9c8c..f39ee747d9 100644
--- a/src/effects/SkAvoidXfermode.cpp
+++ b/src/effects/SkAvoidXfermode.cpp
@@ -1,4 +1,3 @@
-
/*
* Copyright 2006 The Android Open Source Project
*
@@ -6,13 +5,11 @@
* found in the LICENSE file.
*/
-
#include "SkAvoidXfermode.h"
#include "SkColorPriv.h"
#include "SkFlattenableBuffers.h"
-SkAvoidXfermode::SkAvoidXfermode(SkColor opColor, U8CPU tolerance, Mode mode)
-{
+SkAvoidXfermode::SkAvoidXfermode(SkColor opColor, U8CPU tolerance, Mode mode) {
if (tolerance > 255) {
tolerance = 255;
}
@@ -23,15 +20,13 @@ SkAvoidXfermode::SkAvoidXfermode(SkColor opColor, U8CPU tolerance, Mode mode)
}
SkAvoidXfermode::SkAvoidXfermode(SkFlattenableReadBuffer& buffer)
- : INHERITED(buffer)
-{
+ : INHERITED(buffer) {
fOpColor = buffer.readColor();
fDistMul = buffer.readUInt();
fMode = (Mode)buffer.readUInt();
}
-void SkAvoidXfermode::flatten(SkFlattenableWriteBuffer& buffer) const
-{
+void SkAvoidXfermode::flatten(SkFlattenableWriteBuffer& buffer) const {
this->INHERITED::flatten(buffer);
buffer.writeColor(fOpColor);
@@ -40,8 +35,7 @@ void SkAvoidXfermode::flatten(SkFlattenableWriteBuffer& buffer) const
}
// returns 0..31
-static unsigned color_dist16(uint16_t c, unsigned r, unsigned g, unsigned b)
-{
+static unsigned color_dist16(uint16_t c, unsigned r, unsigned g, unsigned b) {
SkASSERT(r <= SK_R16_MASK);
SkASSERT(g <= SK_G16_MASK);
SkASSERT(b <= SK_B16_MASK);
@@ -54,8 +48,7 @@ static unsigned color_dist16(uint16_t c, unsigned r, unsigned g, unsigned b)
}
// returns 0..15
-static unsigned color_dist4444(uint16_t c, unsigned r, unsigned g, unsigned b)
-{
+static unsigned color_dist4444(uint16_t c, unsigned r, unsigned g, unsigned b) {
SkASSERT(r <= 0xF);
SkASSERT(g <= 0xF);
SkASSERT(b <= 0xF);
@@ -68,8 +61,7 @@ static unsigned color_dist4444(uint16_t c, unsigned r, unsigned g, unsigned b)
}
// returns 0..255
-static unsigned color_dist32(SkPMColor c, U8CPU r, U8CPU g, U8CPU b)
-{
+static unsigned color_dist32(SkPMColor c, U8CPU r, U8CPU g, U8CPU b) {
SkASSERT(r <= 0xFF);
SkASSERT(g <= 0xFF);
SkASSERT(b <= 0xFF);
@@ -81,8 +73,7 @@ static unsigned color_dist32(SkPMColor c, U8CPU r, U8CPU g, U8CPU b)
return SkMax32(dr, SkMax32(dg, db));
}
-static int scale_dist_14(int dist, uint32_t mul, uint32_t sub)
-{
+static int scale_dist_14(int dist, uint32_t mul, uint32_t sub) {
int tmp = dist * mul - sub;
int result = (tmp + (1 << 13)) >> 14;
@@ -94,8 +85,7 @@ static inline unsigned Accurate255To256(unsigned x) {
}
void SkAvoidXfermode::xfer32(SkPMColor dst[], const SkPMColor src[], int count,
- const SkAlpha aa[])
-{
+ const SkAlpha aa[]) const {
unsigned opR = SkColorGetR(fOpColor);
unsigned opG = SkColorGetG(fOpColor);
unsigned opB = SkColorGetB(fOpColor);
@@ -134,8 +124,7 @@ void SkAvoidXfermode::xfer32(SkPMColor dst[], const SkPMColor src[], int count,
}
}
-static inline U16CPU SkBlend3216(SkPMColor src, U16CPU dst, unsigned scale)
-{
+static inline U16CPU SkBlend3216(SkPMColor src, U16CPU dst, unsigned scale) {
SkASSERT(scale <= 32);
scale <<= 3;
@@ -145,8 +134,7 @@ static inline U16CPU SkBlend3216(SkPMColor src, U16CPU dst, unsigned scale)
}
void SkAvoidXfermode::xfer16(uint16_t dst[], const SkPMColor src[], int count,
- const SkAlpha aa[])
-{
+ const SkAlpha aa[]) const {
unsigned opR = SkColorGetR(fOpColor) >> (8 - SK_R16_BITS);
unsigned opG = SkColorGetG(fOpColor) >> (8 - SK_G16_BITS);
unsigned opB = SkColorGetB(fOpColor) >> (8 - SK_R16_BITS);
@@ -186,8 +174,7 @@ void SkAvoidXfermode::xfer16(uint16_t dst[], const SkPMColor src[], int count,
}
void SkAvoidXfermode::xfer4444(uint16_t dst[], const SkPMColor src[], int count,
- const SkAlpha aa[])
-{
+ const SkAlpha aa[]) const {
unsigned opR = SkColorGetR(fOpColor) >> 4;
unsigned opG = SkColorGetG(fOpColor) >> 4;
unsigned opB = SkColorGetB(fOpColor) >> 4;
@@ -226,7 +213,7 @@ void SkAvoidXfermode::xfer4444(uint16_t dst[], const SkPMColor src[], int count,
}
}
-void SkAvoidXfermode::xferA8(SkAlpha dst[], const SkPMColor src[], int count, const SkAlpha aa[])
-{
+void SkAvoidXfermode::xferA8(SkAlpha dst[], const SkPMColor src[], int count,
+ const SkAlpha aa[]) const {
// override in subclass
}
diff --git a/src/effects/SkBlendImageFilter.cpp b/src/effects/SkBlendImageFilter.cpp
index 1fa3c0da6b..1394a0a0b5 100644
--- a/src/effects/SkBlendImageFilter.cpp
+++ b/src/effects/SkBlendImageFilter.cpp
@@ -12,7 +12,10 @@
#if SK_SUPPORT_GPU
#include "SkGr.h"
#include "SkGrPixelRef.h"
-#include "gl/GrGLProgramStage.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
+#include "effects/GrSingleTextureEffect.h"
+#include "GrTBackendEffectFactory.h"
#endif
namespace {
@@ -52,7 +55,7 @@ SkPMColor multiply_proc(SkPMColor src, SkPMColor dst) {
///////////////////////////////////////////////////////////////////////////////
SkBlendImageFilter::SkBlendImageFilter(SkBlendImageFilter::Mode mode, SkImageFilter* background, SkImageFilter* foreground)
- : INHERITED(2, background, foreground), fMode(mode)
+ : INHERITED(background, foreground), fMode(mode)
{
}
@@ -110,39 +113,42 @@ bool SkBlendImageFilter::onFilterImage(Proxy* proxy,
///////////////////////////////////////////////////////////////////////////////
#if SK_SUPPORT_GPU
-class GrGLBlendEffect : public GrGLProgramStage {
+class GrGLBlendEffect : public GrGLEffect {
public:
- GrGLBlendEffect(const GrProgramStageFactory& factory,
- const GrCustomStage& stage);
+ GrGLBlendEffect(const GrBackendEffectFactory& factory,
+ const GrEffect& effect);
virtual ~GrGLBlendEffect();
- virtual void emitFS(GrGLShaderBuilder* builder,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray&) SK_OVERRIDE;
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrEffectStage&,
+ EffectKey,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
- virtual void emitVS(GrGLShaderBuilder* builder,
- const char* vertexCoords) SK_OVERRIDE {}
+ static inline EffectKey GenKey(const GrEffectStage&, const GrGLCaps&);
- static inline StageKey GenKey(const GrCustomStage& s, const GrGLCaps&);
+ virtual void setData(const GrGLUniformManager&, const GrEffectStage&);
private:
- typedef GrGLProgramStage INHERITED;
+ typedef GrGLEffect INHERITED;
SkBlendImageFilter::Mode fMode;
+ GrGLEffectMatrix fEffectMatrix;
};
///////////////////////////////////////////////////////////////////////////////
class GrBlendEffect : public GrSingleTextureEffect {
public:
- GrBlendEffect(SkBlendImageFilter::Mode mode, GrTexture* foreground);
+ GrBlendEffect(SkBlendImageFilter::Mode mode, GrTexture* foreground, const SkMatrix&);
virtual ~GrBlendEffect();
- virtual bool isEqual(const GrCustomStage&) const SK_OVERRIDE;
- const GrProgramStageFactory& getFactory() const;
+ virtual bool isEqual(const GrEffect&) const SK_OVERRIDE;
+ const GrBackendEffectFactory& getFactory() const;
SkBlendImageFilter::Mode mode() const { return fMode; }
- typedef GrGLBlendEffect GLProgramStage;
+ typedef GrGLBlendEffect GLEffect;
static const char* Name() { return "Blend"; }
private:
@@ -203,54 +209,64 @@ GrTexture* SkBlendImageFilter::onFilterImageGPU(Proxy* proxy, GrTexture* src, co
GrContext::AutoRenderTarget art(context, dst->asRenderTarget());
GrContext::AutoClip ac(context, rect);
- GrMatrix sampleM;
- sampleM.setIDiv(background->width(), background->height());
+ SkMatrix backgroundTexMatrix, foregroundTexMatrix;
+ backgroundTexMatrix.setIDiv(background->width(), background->height());
+ foregroundTexMatrix.setIDiv(foreground->width(), foreground->height());
GrPaint paint;
- paint.colorSampler(0)->setCustomStage(SkNEW_ARGS(GrSingleTextureEffect, (background.get())), sampleM)->unref();
- paint.colorSampler(1)->setCustomStage(SkNEW_ARGS(GrBlendEffect, (fMode, foreground.get())), sampleM)->unref();
+ paint.colorStage(0)->setEffect(
+ SkNEW_ARGS(GrSingleTextureEffect, (background.get(), backgroundTexMatrix)))->unref();
+ paint.colorStage(1)->setEffect(
+ SkNEW_ARGS(GrBlendEffect, (fMode, foreground.get(), foregroundTexMatrix)))->unref();
context->drawRect(paint, rect);
return dst;
}
///////////////////////////////////////////////////////////////////////////////
-GrBlendEffect::GrBlendEffect(SkBlendImageFilter::Mode mode, GrTexture* foreground)
- : INHERITED(foreground), fMode(mode) {
+GrBlendEffect::GrBlendEffect(SkBlendImageFilter::Mode mode,
+ GrTexture* foreground,
+ const SkMatrix& matrix)
+ : INHERITED(foreground, matrix), fMode(mode) {
}
GrBlendEffect::~GrBlendEffect() {
}
-bool GrBlendEffect::isEqual(const GrCustomStage& sBase) const {
+bool GrBlendEffect::isEqual(const GrEffect& sBase) const {
const GrBlendEffect& s = static_cast<const GrBlendEffect&>(sBase);
- return INHERITED::isEqual(sBase) &&
- fMode == s.fMode;
+ return INHERITED::isEqual(sBase) && fMode == s.fMode;
}
-const GrProgramStageFactory& GrBlendEffect::getFactory() const {
- return GrTProgramStageFactory<GrBlendEffect>::getInstance();
+const GrBackendEffectFactory& GrBlendEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrBlendEffect>::getInstance();
}
///////////////////////////////////////////////////////////////////////////////
-GrGLBlendEffect::GrGLBlendEffect(const GrProgramStageFactory& factory,
- const GrCustomStage& stage)
- : GrGLProgramStage(factory),
- fMode(static_cast<const GrBlendEffect&>(stage).mode()) {
+GrGLBlendEffect::GrGLBlendEffect(const GrBackendEffectFactory& factory,
+ const GrEffect& effect)
+ : INHERITED(factory),
+ fMode(static_cast<const GrBlendEffect&>(effect).mode()) {
}
GrGLBlendEffect::~GrGLBlendEffect() {
}
-void GrGLBlendEffect::emitFS(GrGLShaderBuilder* builder,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray& samplers) {
+void GrGLBlendEffect::emitCode(GrGLShaderBuilder* builder,
+ const GrEffectStage&,
+ EffectKey key,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ const char* coords;
+ GrSLType coordsType = fEffectMatrix.emitCode(builder, key, vertexCoords, &coords);
+
SkString* code = &builder->fFSCode;
const char* bgColor = inputColor;
const char* fgColor = "fgColor";
code->appendf("\t\tvec4 %s = ", fgColor);
- builder->appendTextureLookup(code, samplers[0]);
+ builder->appendTextureLookup(code, samplers[0], coords, coordsType);
code->append(";\n");
code->appendf("\t\t%s.a = 1.0 - (1.0 - %s.a) * (1.0 - %s.b);\n", outputColor, bgColor, fgColor);
switch (fMode) {
@@ -272,7 +288,16 @@ void GrGLBlendEffect::emitFS(GrGLShaderBuilder* builder,
}
}
-GrGLProgramStage::StageKey GrGLBlendEffect::GenKey(const GrCustomStage& s, const GrGLCaps&) {
- return static_cast<const GrBlendEffect&>(s).mode();
+void GrGLBlendEffect::setData(const GrGLUniformManager& uman, const GrEffectStage& stage) {
+ const GrBlendEffect& blend = static_cast<const GrBlendEffect&>(*stage.getEffect());
+ fEffectMatrix.setData(uman, blend.getMatrix(), stage.getCoordChangeMatrix(), blend.texture(0));
+}
+
+GrGLEffect::EffectKey GrGLBlendEffect::GenKey(const GrEffectStage& stage, const GrGLCaps&) {
+ const GrBlendEffect& blend = static_cast<const GrBlendEffect&>(*stage.getEffect());
+ EffectKey key =
+ GrGLEffectMatrix::GenKey(blend.getMatrix(), stage.getCoordChangeMatrix(), blend.texture(0));
+ key |= (blend.mode() << GrGLEffectMatrix::kKeyBits);
+ return key;
}
#endif
diff --git a/src/effects/SkBlurMask.cpp b/src/effects/SkBlurMask.cpp
index f545d8c928..658b0fd47e 100644
--- a/src/effects/SkBlurMask.cpp
+++ b/src/effects/SkBlurMask.cpp
@@ -12,6 +12,306 @@
#include "SkTemplates.h"
#include "SkEndian.h"
+#define UNROLL_SEPARABLE_LOOPS
+
+/**
+ * This function performs a box blur in X, of the given radius. If the
+ * "transpose" parameter is true, it will transpose the pixels on write,
+ * such that X and Y are swapped. Reads are always performed from contiguous
+ * memory in X, for speed. The destination buffer (dst) must be at least
+ * (width + leftRadius + rightRadius) * height bytes in size.
+ */
+static int boxBlur(const uint8_t* src, int src_y_stride, uint8_t* dst,
+ int leftRadius, int rightRadius, int width, int height,
+ bool transpose)
+{
+ int diameter = leftRadius + rightRadius;
+ int kernelSize = diameter + 1;
+ int border = SkMin32(width, diameter);
+ uint32_t scale = (1 << 24) / kernelSize;
+ int new_width = width + SkMax32(leftRadius, rightRadius) * 2;
+ int dst_x_stride = transpose ? height : 1;
+ int dst_y_stride = transpose ? 1 : new_width;
+ for (int y = 0; y < height; ++y) {
+ int sum = 0;
+ uint8_t* dptr = dst + y * dst_y_stride;
+ const uint8_t* right = src + y * src_y_stride;
+ const uint8_t* left = right;
+ for (int x = 0; x < rightRadius - leftRadius; x++) {
+ *dptr = 0;
+ dptr += dst_x_stride;
+ }
+#define LEFT_BORDER_ITER \
+ sum += *right++; \
+ *dptr = (sum * scale) >> 24; \
+ dptr += dst_x_stride;
+
+ int x = 0;
+#ifdef UNROLL_SEPARABLE_LOOPS
+ for (; x < border - 16; x += 16) {
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ }
+#endif
+ for (; x < border; ++x) {
+ LEFT_BORDER_ITER
+ }
+#undef LEFT_BORDER_ITER
+#define TRIVIAL_ITER \
+ *dptr = (sum * scale) >> 24; \
+ dptr += dst_x_stride;
+ x = width;
+#ifdef UNROLL_SEPARABLE_LOOPS
+ for (; x < diameter - 16; x += 16) {
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ }
+#endif
+ for (; x < diameter; ++x) {
+ TRIVIAL_ITER
+ }
+#undef TRIVIAL_ITER
+#define CENTER_ITER \
+ sum += *right++; \
+ *dptr = (sum * scale) >> 24; \
+ sum -= *left++; \
+ dptr += dst_x_stride;
+
+ x = diameter;
+#ifdef UNROLL_SEPARABLE_LOOPS
+ for (; x < width - 16; x += 16) {
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ }
+#endif
+ for (; x < width; ++x) {
+ CENTER_ITER
+ }
+#undef CENTER_ITER
+#define RIGHT_BORDER_ITER \
+ *dptr = (sum * scale) >> 24; \
+ sum -= *left++; \
+ dptr += dst_x_stride;
+
+ x = 0;
+#ifdef UNROLL_SEPARABLE_LOOPS
+ for (; x < border - 16; x += 16) {
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ }
+#endif
+ for (; x < border; ++x) {
+ RIGHT_BORDER_ITER
+ }
+#undef RIGHT_BORDER_ITER
+ for (int x = 0; x < leftRadius - rightRadius; x++) {
+ *dptr = 0;
+ dptr += dst_x_stride;
+ }
+ SkASSERT(sum == 0);
+ }
+ return new_width;
+}
+
+/**
+ * This variant of the box blur handles blurring of non-integer radii. It
+ * keeps two running sums: an outer sum for the rounded-up kernel radius, and
+ * an inner sum for the rounded-down kernel radius. For each pixel, it linearly
+ * interpolates between them. In float this would be:
+ * outer_weight * outer_sum / kernelSize +
+ * (1.0 - outer_weight) * innerSum / (kernelSize - 2)
+ */
+static int boxBlurInterp(const uint8_t* src, int src_y_stride, uint8_t* dst,
+ int radius, int width, int height,
+ bool transpose, uint8_t outer_weight)
+{
+ int diameter = radius * 2;
+ int kernelSize = diameter + 1;
+ int border = SkMin32(width, diameter);
+ int inner_weight = 255 - outer_weight;
+ outer_weight += outer_weight >> 7;
+ inner_weight += inner_weight >> 7;
+ uint32_t outer_scale = (outer_weight << 16) / kernelSize;
+ uint32_t inner_scale = (inner_weight << 16) / (kernelSize - 2);
+ int new_width = width + diameter;
+ int dst_x_stride = transpose ? height : 1;
+ int dst_y_stride = transpose ? 1 : new_width;
+ for (int y = 0; y < height; ++y) {
+ int outer_sum = 0, inner_sum = 0;
+ uint8_t* dptr = dst + y * dst_y_stride;
+ const uint8_t* right = src + y * src_y_stride;
+ const uint8_t* left = right;
+ int x = 0;
+
+#define LEFT_BORDER_ITER \
+ inner_sum = outer_sum; \
+ outer_sum += *right++; \
+ *dptr = (outer_sum * outer_scale + inner_sum * inner_scale) >> 24; \
+ dptr += dst_x_stride;
+
+#ifdef UNROLL_SEPARABLE_LOOPS
+ for (;x < border - 16; x += 16) {
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ }
+#endif
+
+ for (;x < border; x++) {
+ LEFT_BORDER_ITER
+ }
+#undef LEFT_BORDER_ITER
+ for (int x = width; x < diameter; ++x) {
+ *dptr = (outer_sum * outer_scale + inner_sum * inner_scale) >> 24;
+ dptr += dst_x_stride;
+ }
+ x = diameter;
+
+#define CENTER_ITER \
+ inner_sum = outer_sum - *left; \
+ outer_sum += *right++; \
+ *dptr = (outer_sum * outer_scale + inner_sum * inner_scale) >> 24; \
+ dptr += dst_x_stride; \
+ outer_sum -= *left++;
+
+#ifdef UNROLL_SEPARABLE_LOOPS
+ for (; x < width - 16; x += 16) {
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ }
+#endif
+ for (; x < width; ++x) {
+ CENTER_ITER
+ }
+#undef CENTER_ITER
+
+ #define RIGHT_BORDER_ITER \
+ inner_sum = outer_sum - *left++; \
+ *dptr = (outer_sum * outer_scale + inner_sum * inner_scale) >> 24; \
+ dptr += dst_x_stride; \
+ outer_sum = inner_sum;
+
+ x = 0;
+#ifdef UNROLL_SEPARABLE_LOOPS
+ for (; x < border - 16; x += 16) {
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ }
+#endif
+ for (; x < border; x++) {
+ RIGHT_BORDER_ITER
+ }
+#undef RIGHT_BORDER_ITER
+ SkASSERT(outer_sum == 0 && inner_sum == 0);
+ }
+ return new_width;
+}
+
+static void get_adjusted_radii(SkScalar passRadius, int *loRadius, int *hiRadius)
+{
+ *loRadius = *hiRadius = SkScalarCeil(passRadius);
+ if (SkIntToScalar(*hiRadius) - passRadius > SkFloatToScalar(0.5f)) {
+ *loRadius = *hiRadius - 1;
+ }
+}
+
// Unrolling the integer blur kernel seems to give us a ~15% speedup on Windows,
// breakeven on Mac, and ~15% slowdown on Linux.
// Reading a word at a time when bulding the sum buffer seems to give
@@ -553,17 +853,19 @@ void SkMask_FreeImage(uint8_t* image) {
bool SkBlurMask::Blur(SkMask* dst, const SkMask& src,
SkScalar radius, Style style, Quality quality,
- SkIPoint* margin)
+ SkIPoint* margin, bool separable)
{
if (src.fFormat != SkMask::kA8_Format) {
return false;
}
// Force high quality off for small radii (performance)
- if (radius < SkIntToScalar(3)) quality = kLow_Quality;
+ if (radius < SkIntToScalar(3)) {
+ quality = kLow_Quality;
+ }
// highQuality: use three box blur passes as a cheap way to approximate a Gaussian blur
- int passCount = (quality == kHigh_Quality) ? 3 : 1;
+ int passCount = (kHigh_Quality == quality) ? 3 : 1;
SkScalar passRadius = SkScalarDiv(radius, SkScalarSqrt(SkIntToScalar(passCount)));
int rx = SkScalarCeil(passRadius);
@@ -602,7 +904,43 @@ bool SkBlurMask::Blur(SkMask* dst, const SkMask& src,
SkAutoTCallVProc<uint8_t, SkMask_FreeImage> autoCall(dp);
// build the blurry destination
- {
+ if (separable) {
+ SkAutoTMalloc<uint8_t> tmpBuffer(dstSize);
+ uint8_t* tp = tmpBuffer.get();
+ int w = sw, h = sh;
+
+ if (outer_weight == 255) {
+ int loRadius, hiRadius;
+ get_adjusted_radii(passRadius, &loRadius, &hiRadius);
+ if (kHigh_Quality == quality) {
+ // Do three X blurs, with a transpose on the final one.
+ w = boxBlur(sp, src.fRowBytes, tp, loRadius, hiRadius, w, h, false);
+ w = boxBlur(tp, w, dp, hiRadius, loRadius, w, h, false);
+ w = boxBlur(dp, w, tp, hiRadius, hiRadius, w, h, true);
+ // Do three Y blurs, with a transpose on the final one.
+ h = boxBlur(tp, h, dp, loRadius, hiRadius, h, w, false);
+ h = boxBlur(dp, h, tp, hiRadius, loRadius, h, w, false);
+ h = boxBlur(tp, h, dp, hiRadius, hiRadius, h, w, true);
+ } else {
+ w = boxBlur(sp, src.fRowBytes, tp, rx, rx, w, h, true);
+ h = boxBlur(tp, h, dp, ry, ry, h, w, true);
+ }
+ } else {
+ if (kHigh_Quality == quality) {
+ // Do three X blurs, with a transpose on the final one.
+ w = boxBlurInterp(sp, src.fRowBytes, tp, rx, w, h, false, outer_weight);
+ w = boxBlurInterp(tp, w, dp, rx, w, h, false, outer_weight);
+ w = boxBlurInterp(dp, w, tp, rx, w, h, true, outer_weight);
+ // Do three Y blurs, with a transpose on the final one.
+ h = boxBlurInterp(tp, h, dp, ry, h, w, false, outer_weight);
+ h = boxBlurInterp(dp, h, tp, ry, h, w, false, outer_weight);
+ h = boxBlurInterp(tp, h, dp, ry, h, w, true, outer_weight);
+ } else {
+ w = boxBlurInterp(sp, src.fRowBytes, tp, rx, w, h, true, outer_weight);
+ h = boxBlurInterp(tp, h, dp, ry, h, w, true, outer_weight);
+ }
+ }
+ } else {
const size_t storageW = sw + 2 * (passCount - 1) * rx + 1;
const size_t storageH = sh + 2 * (passCount - 1) * ry + 1;
SkAutoTMalloc<uint32_t> storage(storageW * storageH);
@@ -616,7 +954,7 @@ bool SkBlurMask::Blur(SkMask* dst, const SkMask& src,
apply_kernel_interp(dp, rx, ry, sumBuffer, sw, sh, outer_weight);
}
- if (quality == kHigh_Quality) {
+ if (kHigh_Quality == quality) {
//pass2: dp is source, tmpBuffer is destination
int tmp_sw = sw + 2 * rx;
int tmp_sh = sh + 2 * ry;
@@ -670,3 +1008,16 @@ bool SkBlurMask::Blur(SkMask* dst, const SkMask& src,
return true;
}
+bool SkBlurMask::BlurSeparable(SkMask* dst, const SkMask& src,
+ SkScalar radius, Style style, Quality quality,
+ SkIPoint* margin)
+{
+ return SkBlurMask::Blur(dst, src, radius, style, quality, margin, true);
+}
+
+bool SkBlurMask::Blur(SkMask* dst, const SkMask& src,
+ SkScalar radius, Style style, Quality quality,
+ SkIPoint* margin)
+{
+ return SkBlurMask::Blur(dst, src, radius, style, quality, margin, false);
+}
diff --git a/src/effects/SkBlurMask.h b/src/effects/SkBlurMask.h
index 3709deeb58..cfd7e67bd0 100644
--- a/src/effects/SkBlurMask.h
+++ b/src/effects/SkBlurMask.h
@@ -31,6 +31,13 @@ public:
static bool Blur(SkMask* dst, const SkMask& src,
SkScalar radius, Style style, Quality quality,
SkIPoint* margin = NULL);
+ static bool BlurSeparable(SkMask* dst, const SkMask& src,
+ SkScalar radius, Style style, Quality quality,
+ SkIPoint* margin = NULL);
+private:
+ static bool Blur(SkMask* dst, const SkMask& src,
+ SkScalar radius, Style style, Quality quality,
+ SkIPoint* margin, bool separable);
};
#endif
diff --git a/src/effects/SkBlurMaskFilter.cpp b/src/effects/SkBlurMaskFilter.cpp
index 4af3bfc039..c49a785024 100644
--- a/src/effects/SkBlurMaskFilter.cpp
+++ b/src/effects/SkBlurMaskFilter.cpp
@@ -6,7 +6,6 @@
* found in the LICENSE file.
*/
-
#include "SkBlurMaskFilter.h"
#include "SkBlurMask.h"
#include "SkFlattenableBuffers.h"
@@ -18,14 +17,19 @@ public:
uint32_t flags);
// overrides from SkMaskFilter
- virtual SkMask::Format getFormat() SK_OVERRIDE;
+ virtual SkMask::Format getFormat() const SK_OVERRIDE;
virtual bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&,
- SkIPoint* margin) SK_OVERRIDE;
+ SkIPoint* margin) const SK_OVERRIDE;
virtual BlurType asABlur(BlurInfo*) const SK_OVERRIDE;
- virtual void computeFastBounds(const SkRect& src, SkRect* dst) SK_OVERRIDE;
+ virtual void computeFastBounds(const SkRect&, SkRect*) const SK_OVERRIDE;
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkBlurMaskFilterImpl)
+protected:
+ virtual FilterReturn filterRectsToNine(const SkRect[], int count, const SkMatrix&,
+ const SkIRect& clipBounds,
+ NinePatch*) const SK_OVERRIDE;
+
private:
SkScalar fRadius;
SkBlurMaskFilter::BlurStyle fBlurStyle;
@@ -70,12 +74,13 @@ SkBlurMaskFilterImpl::SkBlurMaskFilterImpl(SkScalar radius,
SkASSERT(flags <= SkBlurMaskFilter::kAll_BlurFlag);
}
-SkMask::Format SkBlurMaskFilterImpl::getFormat() {
+SkMask::Format SkBlurMaskFilterImpl::getFormat() const {
return SkMask::kA8_Format;
}
bool SkBlurMaskFilterImpl::filterMask(SkMask* dst, const SkMask& src,
- const SkMatrix& matrix, SkIPoint* margin) {
+ const SkMatrix& matrix,
+ SkIPoint* margin) const{
SkScalar radius;
if (fBlurFlags & SkBlurMaskFilter::kIgnoreTransform_BlurFlag) {
radius = fRadius;
@@ -92,11 +97,161 @@ bool SkBlurMaskFilterImpl::filterMask(SkMask* dst, const SkMask& src,
(fBlurFlags & SkBlurMaskFilter::kHighQuality_BlurFlag) ?
SkBlurMask::kHigh_Quality : SkBlurMask::kLow_Quality;
+#ifndef SK_DISABLE_SEPARABLE_MASK_BLUR
+ return SkBlurMask::BlurSeparable(dst, src, radius, (SkBlurMask::Style)fBlurStyle,
+ blurQuality, margin);
+#else
return SkBlurMask::Blur(dst, src, radius, (SkBlurMask::Style)fBlurStyle,
blurQuality, margin);
+#endif
+}
+
+#include "SkCanvas.h"
+
+static bool drawRectsIntoMask(const SkRect rects[], int count, SkMask* mask) {
+ rects[0].roundOut(&mask->fBounds);
+ mask->fRowBytes = SkAlign4(mask->fBounds.width());
+ mask->fFormat = SkMask::kA8_Format;
+ size_t size = mask->computeImageSize();
+ mask->fImage = SkMask::AllocImage(size);
+ if (NULL == mask->fImage) {
+ return false;
+ }
+ sk_bzero(mask->fImage, size);
+
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kA8_Config,
+ mask->fBounds.width(), mask->fBounds.height(),
+ mask->fRowBytes);
+ bitmap.setPixels(mask->fImage);
+
+ SkCanvas canvas(bitmap);
+ canvas.translate(-SkIntToScalar(mask->fBounds.left()),
+ -SkIntToScalar(mask->fBounds.top()));
+
+ SkPaint paint;
+ paint.setAntiAlias(true);
+
+ if (1 == count) {
+ canvas.drawRect(rects[0], paint);
+ } else {
+ // todo: do I need a fast way to do this?
+ SkPath path;
+ path.addRect(rects[0]);
+ path.addRect(rects[1]);
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ canvas.drawPath(path, paint);
+ }
+ return true;
+}
+
+static bool rect_exceeds(const SkRect& r, SkScalar v) {
+ return r.fLeft < -v || r.fTop < -v || r.fRight > v || r.fBottom > v ||
+ r.width() > v || r.height() > v;
+}
+
+SkMaskFilter::FilterReturn
+SkBlurMaskFilterImpl::filterRectsToNine(const SkRect rects[], int count,
+ const SkMatrix& matrix,
+ const SkIRect& clipBounds,
+ NinePatch* patch) const {
+ if (count < 1 || count > 2) {
+ return kUnimplemented_FilterReturn;
+ }
+
+ // TODO: report correct metrics for innerstyle, where we do not grow the
+ // total bounds, but we do need an inset the size of our blur-radius
+ if (SkBlurMaskFilter::kInner_BlurStyle == fBlurStyle) {
+ return kUnimplemented_FilterReturn;
+ }
+
+ // TODO: take clipBounds into account to limit our coordinates up front
+ // for now, just skip too-large src rects (to take the old code path).
+ if (rect_exceeds(rects[0], SkIntToScalar(32767))) {
+ return kUnimplemented_FilterReturn;
+ }
+
+ SkIPoint margin;
+ SkMask srcM, dstM;
+ rects[0].roundOut(&srcM.fBounds);
+ srcM.fImage = NULL;
+ srcM.fFormat = SkMask::kA8_Format;
+ srcM.fRowBytes = 0;
+ if (!this->filterMask(&dstM, srcM, matrix, &margin)) {
+ return kFalse_FilterReturn;
+ }
+
+ /*
+ * smallR is the smallest version of 'rect' that will still guarantee that
+ * we get the same blur results on all edges, plus 1 center row/col that is
+ * representative of the extendible/stretchable edges of the ninepatch.
+ * Since our actual edge may be fractional we inset 1 more to be sure we
+ * don't miss any interior blur.
+ * x is an added pixel of blur, and { and } are the (fractional) edge
+ * pixels from the original rect.
+ *
+ * x x { x x .... x x } x x
+ *
+ * Thus, in this case, we inset by a total of 5 (on each side) beginning
+ * with our outer-rect (dstM.fBounds)
+ */
+ SkRect smallR[2];
+ SkIPoint center;
+
+ // +2 is from +1 for each edge (to account for possible fractional edges
+ int smallW = dstM.fBounds.width() - srcM.fBounds.width() + 2;
+ int smallH = dstM.fBounds.height() - srcM.fBounds.height() + 2;
+ SkIRect innerIR;
+
+ if (1 == count) {
+ innerIR = srcM.fBounds;
+ center.set(smallW, smallH);
+ } else {
+ SkASSERT(2 == count);
+ rects[1].roundIn(&innerIR);
+ center.set(smallW + (innerIR.left() - srcM.fBounds.left()),
+ smallH + (innerIR.top() - srcM.fBounds.top()));
+ }
+
+ // +1 so we get a clean, stretchable, center row/col
+ smallW += 1;
+ smallH += 1;
+
+ // we want the inset amounts to be integral, so we don't change any
+ // fractional phase on the fRight or fBottom of our smallR.
+ const SkScalar dx = SkIntToScalar(innerIR.width() - smallW);
+ const SkScalar dy = SkIntToScalar(innerIR.height() - smallH);
+ if (dx < 0 || dy < 0) {
+ // we're too small, relative to our blur, to break into nine-patch,
+ // so we ask to have our normal filterMask() be called.
+ return kUnimplemented_FilterReturn;
+ }
+
+ smallR[0].set(rects[0].left(), rects[0].top(), rects[0].right() - dx, rects[0].bottom() - dy);
+ SkASSERT(!smallR[0].isEmpty());
+ if (2 == count) {
+ smallR[1].set(rects[1].left(), rects[1].top(),
+ rects[1].right() - dx, rects[1].bottom() - dy);
+ SkASSERT(!smallR[1].isEmpty());
+ }
+
+ if (!drawRectsIntoMask(smallR, count, &srcM)) {
+ return kFalse_FilterReturn;
+ }
+
+ SkAutoMaskFreeImage amf(srcM.fImage);
+
+ if (!this->filterMask(&patch->fMask, srcM, matrix, &margin)) {
+ return kFalse_FilterReturn;
+ }
+ patch->fMask.fBounds.offsetTo(0, 0);
+ patch->fOuterRect = dstM.fBounds;
+ patch->fCenter = center;
+ return kTrue_FilterReturn;
}
-void SkBlurMaskFilterImpl::computeFastBounds(const SkRect& src, SkRect* dst) {
+void SkBlurMaskFilterImpl::computeFastBounds(const SkRect& src,
+ SkRect* dst) const {
dst->set(src.fLeft - fRadius, src.fTop - fRadius,
src.fRight + fRadius, src.fBottom + fRadius);
}
diff --git a/src/effects/SkColorFilterImageFilter.cpp b/src/effects/SkColorFilterImageFilter.cpp
index 22b5d12c9c..369f782d18 100755
--- a/src/effects/SkColorFilterImageFilter.cpp
+++ b/src/effects/SkColorFilterImageFilter.cpp
@@ -56,6 +56,24 @@ bool matrix_needs_clamping(SkScalar matrix[20]) {
};
+SkColorFilterImageFilter* SkColorFilterImageFilter::Create(SkColorFilter* cf,
+ SkImageFilter* input) {
+ SkASSERT(cf);
+ SkScalar colorMatrix[20], inputMatrix[20];
+ SkColorFilter* inputColorFilter;
+ if (input && cf->asColorMatrix(colorMatrix)
+ && (inputColorFilter = input->asColorFilter())
+ && inputColorFilter->asColorMatrix(inputMatrix)
+ && !matrix_needs_clamping(inputMatrix)) {
+ SkScalar combinedMatrix[20];
+ mult_color_matrix(inputMatrix, colorMatrix, combinedMatrix);
+ SkAutoTUnref<SkColorFilter> newCF(SkNEW_ARGS(SkColorMatrixFilter, (combinedMatrix)));
+ return SkNEW_ARGS(SkColorFilterImageFilter, (newCF, input->getInput(0)));
+ } else {
+ return SkNEW_ARGS(SkColorFilterImageFilter, (cf, input));
+ }
+}
+
SkColorFilterImageFilter::SkColorFilterImageFilter(SkColorFilter* cf, SkImageFilter* input) : INHERITED(input), fColorFilter(cf) {
SkASSERT(cf);
SkSafeRef(cf);
@@ -79,37 +97,13 @@ bool SkColorFilterImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& sourc
const SkMatrix& matrix,
SkBitmap* result,
SkIPoint* loc) {
- SkImageFilter* parent = getInput(0);
- SkScalar colorMatrix[20];
- SkBitmap src;
- SkColorFilter* cf;
- if (parent && fColorFilter->asColorMatrix(colorMatrix)) {
- SkColorFilter* parentColorFilter;
- SkScalar parentMatrix[20];
- while (parent && (parentColorFilter = parent->asColorFilter())
- && parentColorFilter->asColorMatrix(parentMatrix)
- && !matrix_needs_clamping(parentMatrix)) {
- SkScalar combinedMatrix[20];
- mult_color_matrix(parentMatrix, colorMatrix, combinedMatrix);
- memcpy(colorMatrix, combinedMatrix, 20 * sizeof(SkScalar));
- parent = parent->getInput(0);
- }
- if (!parent || !parent->filterImage(proxy, source, matrix, &src, loc)) {
- src = source;
- }
- cf = SkNEW_ARGS(SkColorMatrixFilter, (colorMatrix));
- } else {
- src = this->getInputResult(proxy, source, matrix, loc);
- cf = fColorFilter;
- cf->ref();
- }
-
+ SkBitmap src = this->getInputResult(proxy, source, matrix, loc);
SkAutoTUnref<SkDevice> device(proxy->createDevice(src.width(), src.height()));
SkCanvas canvas(device.get());
SkPaint paint;
paint.setXfermodeMode(SkXfermode::kSrc_Mode);
- paint.setColorFilter(cf)->unref();
+ paint.setColorFilter(fColorFilter);
canvas.drawSprite(src, 0, 0, &paint);
*result = device.get()->accessBitmap(false);
diff --git a/src/effects/SkColorFilters.cpp b/src/effects/SkColorFilters.cpp
index aae7868c3c..2036d37c2b 100644
--- a/src/effects/SkColorFilters.cpp
+++ b/src/effects/SkColorFilters.cpp
@@ -35,7 +35,7 @@ public:
bool isModeValid() const { return ILLEGAL_XFERMODE_MODE != fMode; }
SkPMColor getPMColor() const { return fPMColor; }
- virtual bool asColorMode(SkColor* color, SkXfermode::Mode* mode) SK_OVERRIDE {
+ virtual bool asColorMode(SkColor* color, SkXfermode::Mode* mode) const SK_OVERRIDE {
if (ILLEGAL_XFERMODE_MODE == fMode) {
return false;
}
@@ -49,12 +49,12 @@ public:
return true;
}
- virtual uint32_t getFlags() SK_OVERRIDE {
+ virtual uint32_t getFlags() const SK_OVERRIDE {
return fProc16 ? (kAlphaUnchanged_Flag | kHasFilter16_Flag) : 0;
}
virtual void filterSpan(const SkPMColor shader[], int count,
- SkPMColor result[]) SK_OVERRIDE {
+ SkPMColor result[]) const SK_OVERRIDE {
SkPMColor color = fPMColor;
SkXfermodeProc proc = fProc;
@@ -64,7 +64,7 @@ public:
}
virtual void filterSpan16(const uint16_t shader[], int count,
- uint16_t result[]) SK_OVERRIDE {
+ uint16_t result[]) const SK_OVERRIDE {
SkASSERT(this->getFlags() & kHasFilter16_Flag);
SkPMColor color = fPMColor;
@@ -238,7 +238,7 @@ public:
SkLightingColorFilter(SkColor mul, SkColor add) : fMul(mul), fAdd(add) {}
virtual void filterSpan(const SkPMColor shader[], int count,
- SkPMColor result[]) {
+ SkPMColor result[]) const SK_OVERRIDE {
unsigned scaleR = SkAlpha255To256(SkColorGetR(fMul));
unsigned scaleG = SkAlpha255To256(SkColorGetG(fMul));
unsigned scaleB = SkAlpha255To256(SkColorGetB(fMul));
@@ -287,7 +287,7 @@ public:
: INHERITED(mul, add) {}
virtual void filterSpan(const SkPMColor shader[], int count,
- SkPMColor result[]) {
+ SkPMColor result[]) const SK_OVERRIDE {
unsigned addR = SkColorGetR(fAdd);
unsigned addG = SkColorGetG(fAdd);
unsigned addB = SkColorGetB(fAdd);
@@ -322,7 +322,7 @@ public:
: INHERITED(mul, add) {}
virtual void filterSpan(const SkPMColor shader[], int count,
- SkPMColor result[]) {
+ SkPMColor result[]) const SK_OVERRIDE {
unsigned scaleR = SkAlpha255To256(SkColorGetR(fMul));
unsigned scaleG = SkAlpha255To256(SkColorGetG(fMul));
unsigned scaleB = SkAlpha255To256(SkColorGetB(fMul));
@@ -361,12 +361,12 @@ public:
SkASSERT(SkColorGetR(mul) == SkColorGetB(mul));
}
- virtual uint32_t getFlags() {
+ virtual uint32_t getFlags() const {
return this->INHERITED::getFlags() | (kAlphaUnchanged_Flag | kHasFilter16_Flag);
}
virtual void filterSpan16(const uint16_t shader[], int count,
- uint16_t result[]) {
+ uint16_t result[]) const SK_OVERRIDE {
// all mul components are the same
unsigned scale = SkAlpha255To256(SkColorGetR(fMul));
@@ -393,7 +393,7 @@ public:
: INHERITED(mul, add) {}
virtual void filterSpan(const SkPMColor shader[], int count,
- SkPMColor result[]) {
+ SkPMColor result[]) const SK_OVERRIDE {
unsigned scaleR = SkAlpha255To256(SkColorGetR(fMul));
unsigned scaleG = SkAlpha255To256(SkColorGetG(fMul));
unsigned scaleB = SkAlpha255To256(SkColorGetB(fMul));
@@ -435,7 +435,8 @@ public:
}
protected:
- void filterSpan(const SkPMColor src[], int count, SkPMColor result[]) {
+ void filterSpan(const SkPMColor src[], int count, SkPMColor
+ result[]) const SK_OVERRIDE {
if (result != src) {
memcpy(result, src, count * sizeof(SkPMColor));
}
diff --git a/src/effects/SkColorMatrixFilter.cpp b/src/effects/SkColorMatrixFilter.cpp
index 2102e9b81e..e460325de4 100644
--- a/src/effects/SkColorMatrixFilter.cpp
+++ b/src/effects/SkColorMatrixFilter.cpp
@@ -21,11 +21,11 @@ static int32_t rowmul3(const int32_t array[], unsigned r, unsigned g,
return array[0] * r + array[1] * g + array[2] * b + array[4];
}
-static void General(SkColorMatrixFilter::State* state,
- unsigned r, unsigned g, unsigned b, unsigned a) {
- const int32_t* SK_RESTRICT array = state->fArray;
- const int shift = state->fShift;
- int32_t* SK_RESTRICT result = state->fResult;
+static void General(const SkColorMatrixFilter::State& state,
+ unsigned r, unsigned g, unsigned b, unsigned a,
+ int32_t* SK_RESTRICT result) {
+ const int32_t* SK_RESTRICT array = state.fArray;
+ const int shift = state.fShift;
result[0] = rowmul4(&array[0], r, g, b, a) >> shift;
result[1] = rowmul4(&array[5], r, g, b, a) >> shift;
@@ -33,10 +33,10 @@ static void General(SkColorMatrixFilter::State* state,
result[3] = rowmul4(&array[15], r, g, b, a) >> shift;
}
-static void General16(SkColorMatrixFilter::State* state,
- unsigned r, unsigned g, unsigned b, unsigned a) {
- const int32_t* SK_RESTRICT array = state->fArray;
- int32_t* SK_RESTRICT result = state->fResult;
+static void General16(const SkColorMatrixFilter::State& state,
+ unsigned r, unsigned g, unsigned b, unsigned a,
+ int32_t* SK_RESTRICT result) {
+ const int32_t* SK_RESTRICT array = state.fArray;
result[0] = rowmul4(&array[0], r, g, b, a) >> 16;
result[1] = rowmul4(&array[5], r, g, b, a) >> 16;
@@ -44,11 +44,11 @@ static void General16(SkColorMatrixFilter::State* state,
result[3] = rowmul4(&array[15], r, g, b, a) >> 16;
}
-static void AffineAdd(SkColorMatrixFilter::State* state,
- unsigned r, unsigned g, unsigned b, unsigned a) {
- const int32_t* SK_RESTRICT array = state->fArray;
- const int shift = state->fShift;
- int32_t* SK_RESTRICT result = state->fResult;
+static void AffineAdd(const SkColorMatrixFilter::State& state,
+ unsigned r, unsigned g, unsigned b, unsigned a,
+ int32_t* SK_RESTRICT result) {
+ const int32_t* SK_RESTRICT array = state.fArray;
+ const int shift = state.fShift;
result[0] = rowmul3(&array[0], r, g, b) >> shift;
result[1] = rowmul3(&array[5], r, g, b) >> shift;
@@ -56,10 +56,10 @@ static void AffineAdd(SkColorMatrixFilter::State* state,
result[3] = a;
}
-static void AffineAdd16(SkColorMatrixFilter::State* state,
- unsigned r, unsigned g, unsigned b, unsigned a) {
- const int32_t* SK_RESTRICT array = state->fArray;
- int32_t* SK_RESTRICT result = state->fResult;
+static void AffineAdd16(const SkColorMatrixFilter::State& state,
+ unsigned r, unsigned g, unsigned b, unsigned a,
+ int32_t* SK_RESTRICT result) {
+ const int32_t* SK_RESTRICT array = state.fArray;
result[0] = rowmul3(&array[0], r, g, b) >> 16;
result[1] = rowmul3(&array[5], r, g, b) >> 16;
@@ -67,11 +67,11 @@ static void AffineAdd16(SkColorMatrixFilter::State* state,
result[3] = a;
}
-static void ScaleAdd(SkColorMatrixFilter::State* state,
- unsigned r, unsigned g, unsigned b, unsigned a) {
- const int32_t* SK_RESTRICT array = state->fArray;
- const int shift = state->fShift;
- int32_t* SK_RESTRICT result = state->fResult;
+static void ScaleAdd(const SkColorMatrixFilter::State& state,
+ unsigned r, unsigned g, unsigned b, unsigned a,
+ int32_t* SK_RESTRICT result) {
+ const int32_t* SK_RESTRICT array = state.fArray;
+ const int shift = state.fShift;
// cast to (int) to keep the expression signed for the shift
result[0] = (array[0] * (int)r + array[4]) >> shift;
@@ -80,10 +80,10 @@ static void ScaleAdd(SkColorMatrixFilter::State* state,
result[3] = a;
}
-static void ScaleAdd16(SkColorMatrixFilter::State* state,
- unsigned r, unsigned g, unsigned b, unsigned a) {
- const int32_t* SK_RESTRICT array = state->fArray;
- int32_t* SK_RESTRICT result = state->fResult;
+static void ScaleAdd16(const SkColorMatrixFilter::State& state,
+ unsigned r, unsigned g, unsigned b, unsigned a,
+ int32_t* SK_RESTRICT result) {
+ const int32_t* SK_RESTRICT array = state.fArray;
// cast to (int) to keep the expression signed for the shift
result[0] = (array[0] * (int)r + array[4]) >> 16;
@@ -92,11 +92,11 @@ static void ScaleAdd16(SkColorMatrixFilter::State* state,
result[3] = a;
}
-static void Add(SkColorMatrixFilter::State* state,
- unsigned r, unsigned g, unsigned b, unsigned a) {
- const int32_t* SK_RESTRICT array = state->fArray;
- const int shift = state->fShift;
- int32_t* SK_RESTRICT result = state->fResult;
+static void Add(const SkColorMatrixFilter::State& state,
+ unsigned r, unsigned g, unsigned b, unsigned a,
+ int32_t* SK_RESTRICT result) {
+ const int32_t* SK_RESTRICT array = state.fArray;
+ const int shift = state.fShift;
result[0] = r + (array[4] >> shift);
result[1] = g + (array[9] >> shift);
@@ -104,10 +104,10 @@ static void Add(SkColorMatrixFilter::State* state,
result[3] = a;
}
-static void Add16(SkColorMatrixFilter::State* state,
- unsigned r, unsigned g, unsigned b, unsigned a) {
- const int32_t* SK_RESTRICT array = state->fArray;
- int32_t* SK_RESTRICT result = state->fResult;
+static void Add16(const SkColorMatrixFilter::State& state,
+ unsigned r, unsigned g, unsigned b, unsigned a,
+ int32_t* SK_RESTRICT result) {
+ const int32_t* SK_RESTRICT array = state.fArray;
result[0] = r + (array[4] >> 16);
result[1] = g + (array[9] >> 16);
@@ -212,15 +212,15 @@ SkColorMatrixFilter::SkColorMatrixFilter(const SkScalar array[20]) {
this->initState(array);
}
-uint32_t SkColorMatrixFilter::getFlags() {
+uint32_t SkColorMatrixFilter::getFlags() const {
return this->INHERITED::getFlags() | fFlags;
}
void SkColorMatrixFilter::filterSpan(const SkPMColor src[], int count,
- SkPMColor dst[]) {
+ SkPMColor dst[]) const {
Proc proc = fProc;
- State* state = &fState;
- int32_t* result = state->fResult;
+ const State& state = fState;
+ int32_t result[4];
if (NULL == proc) {
if (src != dst) {
@@ -251,7 +251,7 @@ void SkColorMatrixFilter::filterSpan(const SkPMColor src[], int count,
SkASSERT(b <= 255);
}
- proc(state, r, g, b, a);
+ proc(state, r, g, b, a, result);
r = pin(result[0], SK_R32_MASK);
g = pin(result[1], SK_G32_MASK);
@@ -263,12 +263,12 @@ void SkColorMatrixFilter::filterSpan(const SkPMColor src[], int count,
}
void SkColorMatrixFilter::filterSpan16(const uint16_t src[], int count,
- uint16_t dst[]) {
+ uint16_t dst[]) const {
SkASSERT(fFlags & SkColorFilter::kHasFilter16_Flag);
Proc proc = fProc;
- State* state = &fState;
- int32_t* result = state->fResult;
+ const State& state = fState;
+ int32_t result[4];
if (NULL == proc) {
if (src != dst) {
@@ -285,7 +285,7 @@ void SkColorMatrixFilter::filterSpan16(const uint16_t src[], int count,
unsigned g = SkPacked16ToG32(c);
unsigned b = SkPacked16ToB32(c);
- proc(state, r, g, b, 0);
+ proc(state, r, g, b, 0, result);
r = pin(result[0], SK_R32_MASK);
g = pin(result[1], SK_G32_MASK);
@@ -311,9 +311,120 @@ SkColorMatrixFilter::SkColorMatrixFilter(SkFlattenableReadBuffer& buffer)
this->initState(fMatrix.fMat);
}
-bool SkColorMatrixFilter::asColorMatrix(SkScalar matrix[20]) {
+bool SkColorMatrixFilter::asColorMatrix(SkScalar matrix[20]) const {
if (matrix) {
memcpy(matrix, fMatrix.fMat, 20 * sizeof(SkScalar));
}
return true;
}
+
+#if SK_SUPPORT_GPU
+#include "GrEffect.h"
+#include "GrTBackendEffectFactory.h"
+#include "gl/GrGLEffect.h"
+
+class ColorMatrixEffect : public GrEffect {
+public:
+ static const char* Name() { return "Color Matrix"; }
+
+ ColorMatrixEffect(const SkColorMatrix& matrix) : GrEffect(0), fMatrix(matrix) {}
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<ColorMatrixEffect>::getInstance();
+ }
+
+ virtual bool isEqual(const GrEffect& s) const {
+ const ColorMatrixEffect& cme = static_cast<const ColorMatrixEffect&>(s);
+ return cme.fMatrix == fMatrix;
+ }
+
+ GR_DECLARE_EFFECT_TEST;
+
+ class GLEffect : public GrGLEffect {
+ public:
+ // this class always generates the same code.
+ static EffectKey GenKey(const GrEffectStage&, const GrGLCaps&) { return 0; }
+
+ GLEffect(const GrBackendEffectFactory& factory,
+ const GrEffect& effect)
+ : INHERITED(factory)
+ , fMatrixHandle(GrGLUniformManager::kInvalidUniformHandle)
+ , fVectorHandle(GrGLUniformManager::kInvalidUniformHandle) {}
+
+ virtual void emitCode(GrGLShaderBuilder* builder,
+ const GrEffectStage&,
+ EffectKey,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE {
+ fMatrixHandle = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kMat44f_GrSLType,
+ "ColorMatrix");
+ fVectorHandle = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec4f_GrSLType,
+ "ColorMatrixVector");
+
+ if (NULL == inputColor) {
+ // could optimize this case, but we aren't for now.
+ inputColor = GrGLSLOnesVecf(4);
+ }
+ // The max() is to guard against 0 / 0 during unpremul when the incoming color is
+ // transparent black.
+ builder->fFSCode.appendf("\tfloat nonZeroAlpha = max(%s.a, 0.00001);\n", inputColor);
+ builder->fFSCode.appendf("\t%s = %s * vec4(%s.rgb / nonZeroAlpha, nonZeroAlpha) + %s;\n",
+ outputColor,
+ builder->getUniformCStr(fMatrixHandle),
+ inputColor,
+ builder->getUniformCStr(fVectorHandle));
+ builder->fFSCode.appendf("\t%s.rgb *= %s.a;\n", outputColor, outputColor);
+ }
+
+ virtual void setData(const GrGLUniformManager& uniManager,
+ const GrEffectStage& stage) SK_OVERRIDE {
+ const ColorMatrixEffect& cme =
+ static_cast<const ColorMatrixEffect&>(*stage.getEffect());
+ const float* m = cme.fMatrix.fMat;
+ // The GL matrix is transposed from SkColorMatrix.
+ GrGLfloat mt[] = {
+ m[0], m[5], m[10], m[15],
+ m[1], m[6], m[11], m[16],
+ m[2], m[7], m[12], m[17],
+ m[3], m[8], m[13], m[18],
+ };
+ static const float kScale = 1.0f / 255.0f;
+ GrGLfloat vec[] = {
+ m[4] * kScale, m[9] * kScale, m[14] * kScale, m[19] * kScale,
+ };
+ uniManager.setMatrix4fv(fMatrixHandle, 0, 1, mt);
+ uniManager.set4fv(fVectorHandle, 0, 1, vec);
+ }
+
+ private:
+ GrGLUniformManager::UniformHandle fMatrixHandle;
+ GrGLUniformManager::UniformHandle fVectorHandle;
+ };
+
+private:
+ SkColorMatrix fMatrix;
+
+ typedef GrGLEffect INHERITED;
+};
+
+GR_DEFINE_EFFECT_TEST(ColorMatrixEffect);
+
+GrEffect* ColorMatrixEffect::TestCreate(SkRandom* random,
+ GrContext*,
+ GrTexture* dummyTextures[2]) {
+ SkColorMatrix colorMatrix;
+ for (size_t i = 0; i < SK_ARRAY_COUNT(colorMatrix.fMat); ++i) {
+ colorMatrix.fMat[i] = random->nextSScalar1();
+ }
+ return SkNEW_ARGS(ColorMatrixEffect, (colorMatrix));
+}
+
+GrEffect* SkColorMatrixFilter::asNewEffect(GrContext*) const {
+ return SkNEW_ARGS(ColorMatrixEffect, (fMatrix));
+}
+
+#endif
diff --git a/src/effects/SkCornerPathEffect.cpp b/src/effects/SkCornerPathEffect.cpp
index 1ce62ecc03..b63045ddf9 100644
--- a/src/effects/SkCornerPathEffect.cpp
+++ b/src/effects/SkCornerPathEffect.cpp
@@ -12,13 +12,8 @@
#include "SkPoint.h"
#include "SkFlattenableBuffers.h"
-SkCornerPathEffect::SkCornerPathEffect(SkScalar radius) : fRadius(radius)
-{
-}
-
-SkCornerPathEffect::~SkCornerPathEffect()
-{
-}
+SkCornerPathEffect::SkCornerPathEffect(SkScalar radius) : fRadius(radius) {}
+SkCornerPathEffect::~SkCornerPathEffect() {}
static bool ComputeStep(const SkPoint& a, const SkPoint& b, SkScalar radius,
SkPoint* step) {
@@ -36,8 +31,8 @@ static bool ComputeStep(const SkPoint& a, const SkPoint& b, SkScalar radius,
}
bool SkCornerPathEffect::filterPath(SkPath* dst, const SkPath& src,
- SkStrokeRec*) {
- if (fRadius == 0) {
+ SkStrokeRec*) const {
+ if (0 == fRadius) {
return false;
}
diff --git a/src/effects/SkDashPathEffect.cpp b/src/effects/SkDashPathEffect.cpp
index 3add0d7ddc..58d746b3b9 100644
--- a/src/effects/SkDashPathEffect.cpp
+++ b/src/effects/SkDashPathEffect.cpp
@@ -156,7 +156,7 @@ private:
};
bool SkDashPathEffect::filterPath(SkPath* dst, const SkPath& src,
- SkStrokeRec* rec) {
+ SkStrokeRec* rec) const {
// we do nothing if the src wants to be filled, or if our dashlength is 0
if (rec->isFillStyle() || fInitialDashLength < 0) {
return false;
@@ -164,6 +164,7 @@ bool SkDashPathEffect::filterPath(SkPath* dst, const SkPath& src,
SkPathMeasure meas(src, false);
const SkScalar* intervals = fIntervals;
+ SkScalar dashCount = 0;
SpecialLineRec lineRec;
const bool specialLine = lineRec.init(src, dst, rec, meas.getLength(),
@@ -176,6 +177,21 @@ bool SkDashPathEffect::filterPath(SkPath* dst, const SkPath& src,
int index = fInitialDashIndex;
SkScalar scale = SK_Scalar1;
+ // Since the path length / dash length ratio may be arbitrarily large, we can exert
+ // significant memory pressure while attempting to build the filtered path. To avoid this,
+ // we simply give up dashing beyond a certain threshold.
+ //
+ // The original bug report (http://crbug.com/165432) is based on a path yielding more than
+ // 90 million dash segments and crashing the memory allocator. A limit of 1 million
+ // segments seems reasonable: at 2 verbs per segment * 9 bytes per verb, this caps the
+ // maximum dash memory overhead at roughly 17MB per path.
+ static const SkScalar kMaxDashCount = 1000000;
+ dashCount += length * (fCount >> 1) / fIntervalLength;
+ if (dashCount > kMaxDashCount) {
+ dst->reset();
+ return false;
+ }
+
if (fScaleToFit) {
if (fIntervalLength >= length) {
scale = SkScalarDiv(length, fIntervalLength);
@@ -186,8 +202,10 @@ bool SkDashPathEffect::filterPath(SkPath* dst, const SkPath& src,
}
}
- SkScalar distance = 0;
- SkScalar dlen = SkScalarMul(fInitialDashLength, scale);
+ // Using double precision to avoid looping indefinitely due to single precision rounding
+ // (for extreme path_length/dash_length ratios). See test_infinite_dash() unittest.
+ double distance = 0;
+ double dlen = SkScalarMul(fInitialDashLength, scale);
while (distance < length) {
SkASSERT(dlen >= 0);
@@ -196,9 +214,13 @@ bool SkDashPathEffect::filterPath(SkPath* dst, const SkPath& src,
addedSegment = true;
if (specialLine) {
- lineRec.addSegment(distance, distance + dlen, dst);
+ lineRec.addSegment(SkDoubleToScalar(distance),
+ SkDoubleToScalar(distance + dlen),
+ dst);
} else {
- meas.getSegment(distance, distance + dlen, dst, true);
+ meas.getSegment(SkDoubleToScalar(distance),
+ SkDoubleToScalar(distance + dlen),
+ dst, true);
}
}
distance += dlen;
@@ -227,6 +249,199 @@ bool SkDashPathEffect::filterPath(SkPath* dst, const SkPath& src,
return true;
}
+// Currently asPoints is more restrictive then it needs to be. In the future
+// we need to:
+// allow kRound_Cap capping (could allow rotations in the matrix with this)
+// allow paths to be returned
+bool SkDashPathEffect::asPoints(PointData* results,
+ const SkPath& src,
+ const SkStrokeRec& rec,
+ const SkMatrix& matrix) const {
+ // width < 0 -> fill && width == 0 -> hairline so requiring width > 0 rules both out
+ if (fInitialDashLength < 0 || 0 >= rec.getWidth()) {
+ return false;
+ }
+
+ // TODO: this next test could be eased up. We could allow any number of
+ // intervals as long as all the ons match and all the offs match.
+ // Additionally, they do not necessarily need to be integers.
+ // We cannot allow arbitrary intervals since we want the returned points
+ // to be uniformly sized.
+ if (fCount != 2 ||
+ !SkScalarNearlyEqual(fIntervals[0], fIntervals[1]) ||
+ !SkScalarIsInt(fIntervals[0]) ||
+ !SkScalarIsInt(fIntervals[1])) {
+ return false;
+ }
+
+ // TODO: this next test could be eased up. The rescaling should not impact
+ // the equality of the ons & offs. However, we would need to remove the
+ // integer intervals restriction first
+ if (fScaleToFit) {
+ return false;
+ }
+
+ SkPoint pts[2];
+
+ if (!src.isLine(pts)) {
+ return false;
+ }
+
+ // TODO: this test could be eased up to allow circles
+ if (SkPaint::kButt_Cap != rec.getCap()) {
+ return false;
+ }
+
+ // TODO: this test could be eased up for circles. Rotations could be allowed.
+ if (!matrix.rectStaysRect()) {
+ return false;
+ }
+
+ SkScalar length = SkPoint::Distance(pts[1], pts[0]);
+
+ SkVector tangent = pts[1] - pts[0];
+ if (tangent.isZero()) {
+ return false;
+ }
+
+ tangent.scale(SkScalarInvert(length));
+
+ // TODO: make this test for horizontal & vertical lines more robust
+ bool isXAxis = true;
+ if (SK_Scalar1 == tangent.fX || -SK_Scalar1 == tangent.fX) {
+ results->fSize.set(SkScalarHalf(fIntervals[0]), SkScalarHalf(rec.getWidth()));
+ } else if (SK_Scalar1 == tangent.fY || -SK_Scalar1 == tangent.fY) {
+ results->fSize.set(SkScalarHalf(rec.getWidth()), SkScalarHalf(fIntervals[0]));
+ isXAxis = false;
+ } else if (SkPaint::kRound_Cap != rec.getCap()) {
+ // Angled lines don't have axis-aligned boxes.
+ return false;
+ }
+
+ if (NULL != results) {
+ results->fFlags = 0;
+
+ if (SkPaint::kRound_Cap == rec.getCap()) {
+ results->fFlags |= PointData::kCircles_PointFlag;
+ }
+
+ results->fNumPoints = 0;
+ SkScalar len2 = length;
+ bool partialFirst = false;
+ if (fInitialDashLength > 0 || 0 == fInitialDashIndex) {
+ SkASSERT(len2 >= fInitialDashLength);
+ if (0 == fInitialDashIndex) {
+ if (fInitialDashLength > 0) {
+ partialFirst = true;
+ if (fInitialDashLength >= fIntervals[0]) {
+ ++results->fNumPoints; // partial first dash
+ }
+ len2 -= fInitialDashLength;
+ }
+ len2 -= fIntervals[1]; // also skip first space
+ if (len2 < 0) {
+ len2 = 0;
+ }
+ } else {
+ len2 -= fInitialDashLength; // skip initial partial empty
+ }
+ }
+ int numMidPoints = SkScalarFloorToInt(SkScalarDiv(len2, fIntervalLength));
+ results->fNumPoints += numMidPoints;
+ len2 -= numMidPoints * fIntervalLength;
+ bool partialLast = false;
+ if (len2 > 0) {
+ if (len2 < fIntervals[0]) {
+ partialLast = true;
+ } else {
+ ++numMidPoints;
+ ++results->fNumPoints;
+ }
+ }
+
+ results->fPoints = new SkPoint[results->fNumPoints];
+
+ SkScalar distance = 0;
+ int curPt = 0;
+
+ if (fInitialDashLength > 0 || 0 == fInitialDashIndex) {
+ SkASSERT(fInitialDashLength <= length);
+
+ if (0 == fInitialDashIndex) {
+ if (fInitialDashLength > 0) {
+ // partial first block
+ SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles
+ SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, SkScalarHalf(fInitialDashLength));
+ SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, SkScalarHalf(fInitialDashLength));
+ SkScalar halfWidth, halfHeight;
+ if (isXAxis) {
+ halfWidth = SkScalarHalf(fInitialDashLength);
+ halfHeight = SkScalarHalf(rec.getWidth());
+ } else {
+ halfWidth = SkScalarHalf(rec.getWidth());
+ halfHeight = SkScalarHalf(fInitialDashLength);
+ }
+ if (fInitialDashLength < fIntervals[0]) {
+ // This one will not be like the others
+ results->fFirst.addRect(x - halfWidth, y - halfHeight,
+ x + halfWidth, y + halfHeight);
+ } else {
+ SkASSERT(curPt < results->fNumPoints);
+ results->fPoints[curPt].set(x, y);
+ ++curPt;
+ }
+
+ distance += fInitialDashLength;
+ }
+
+ distance += fIntervals[1]; // skip over the next blank block too
+ } else {
+ distance += fInitialDashLength;
+ }
+ }
+
+ if (0 != numMidPoints) {
+ distance += SkScalarHalf(fIntervals[0]);
+
+ for (int i = 0; i < numMidPoints; ++i) {
+ SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, distance);
+ SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, distance);
+
+ SkASSERT(curPt < results->fNumPoints);
+ results->fPoints[curPt].set(x, y);
+ ++curPt;
+
+ distance += fIntervalLength;
+ }
+
+ distance -= SkScalarHalf(fIntervals[0]);
+ }
+
+ if (partialLast) {
+ // partial final block
+ SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles
+ SkScalar temp = length - distance;
+ SkASSERT(temp < fIntervals[0]);
+ SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, distance + SkScalarHalf(temp));
+ SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, distance + SkScalarHalf(temp));
+ SkScalar halfWidth, halfHeight;
+ if (isXAxis) {
+ halfWidth = SkScalarHalf(temp);
+ halfHeight = SkScalarHalf(rec.getWidth());
+ } else {
+ halfWidth = SkScalarHalf(rec.getWidth());
+ halfHeight = SkScalarHalf(temp);
+ }
+ results->fLast.addRect(x - halfWidth, y - halfHeight,
+ x + halfWidth, y + halfHeight);
+ }
+
+ SkASSERT(curPt == results->fNumPoints);
+ }
+
+ return true;
+}
+
SkFlattenable::Factory SkDashPathEffect::getFactory() {
return fInitialDashLength < 0 ? NULL : CreateProc;
}
diff --git a/src/effects/SkDiscretePathEffect.cpp b/src/effects/SkDiscretePathEffect.cpp
index 8a7c307965..a936be9a50 100644
--- a/src/effects/SkDiscretePathEffect.cpp
+++ b/src/effects/SkDiscretePathEffect.cpp
@@ -26,7 +26,7 @@ SkDiscretePathEffect::SkDiscretePathEffect(SkScalar segLength, SkScalar deviatio
}
bool SkDiscretePathEffect::filterPath(SkPath* dst, const SkPath& src,
- SkStrokeRec* rec) {
+ SkStrokeRec* rec) const {
bool doFill = rec->isFillStyle();
SkPathMeasure meas(src, doFill);
diff --git a/src/effects/SkEmbossMaskFilter.cpp b/src/effects/SkEmbossMaskFilter.cpp
index 6480f50e82..0c9158586f 100644
--- a/src/effects/SkEmbossMaskFilter.cpp
+++ b/src/effects/SkEmbossMaskFilter.cpp
@@ -60,12 +60,12 @@ SkEmbossMaskFilter::SkEmbossMaskFilter(const Light& light, SkScalar blurRadius)
normalize(fLight.fDirection);
}
-SkMask::Format SkEmbossMaskFilter::getFormat() {
+SkMask::Format SkEmbossMaskFilter::getFormat() const {
return SkMask::k3D_Format;
}
bool SkEmbossMaskFilter::filterMask(SkMask* dst, const SkMask& src,
- const SkMatrix& matrix, SkIPoint* margin) {
+ const SkMatrix& matrix, SkIPoint* margin) const {
SkScalar radius = matrix.mapRadius(fBlurRadius);
if (!SkBlurMask::Blur(dst, src, radius, SkBlurMask::kInner_Style,
diff --git a/src/effects/SkKernel33MaskFilter.cpp b/src/effects/SkKernel33MaskFilter.cpp
index 1568a4fe0b..0f198cd371 100644
--- a/src/effects/SkKernel33MaskFilter.cpp
+++ b/src/effects/SkKernel33MaskFilter.cpp
@@ -9,12 +9,12 @@
#include "SkColorPriv.h"
#include "SkFlattenableBuffers.h"
-SkMask::Format SkKernel33ProcMaskFilter::getFormat() {
+SkMask::Format SkKernel33ProcMaskFilter::getFormat() const {
return SkMask::kA8_Format;
}
bool SkKernel33ProcMaskFilter::filterMask(SkMask* dst, const SkMask& src,
- const SkMatrix&, SkIPoint* margin) {
+ const SkMatrix&, SkIPoint* margin) const {
// margin???
dst->fImage = NULL;
dst->fBounds = src.fBounds;
@@ -86,7 +86,7 @@ SkKernel33ProcMaskFilter::SkKernel33ProcMaskFilter(SkFlattenableReadBuffer& rb)
///////////////////////////////////////////////////////////////////////////////
-uint8_t SkKernel33MaskFilter::computeValue(uint8_t* const* srcRows) {
+uint8_t SkKernel33MaskFilter::computeValue(uint8_t* const* srcRows) const {
int value = 0;
for (int i = 0; i < 3; i++) {
diff --git a/src/effects/SkLayerRasterizer.cpp b/src/effects/SkLayerRasterizer.cpp
index 7365e12137..ea5808c2b4 100644
--- a/src/effects/SkLayerRasterizer.cpp
+++ b/src/effects/SkLayerRasterizer.cpp
@@ -87,7 +87,7 @@ static bool compute_bounds(const SkDeque& layers, const SkPath& path,
bool SkLayerRasterizer::onRasterize(const SkPath& path, const SkMatrix& matrix,
const SkIRect* clipBounds,
- SkMask* mask, SkMask::CreateMode mode) {
+ SkMask* mask, SkMask::CreateMode mode) const {
if (fLayers.empty()) {
return false;
}
diff --git a/src/effects/SkLightingImageFilter.cpp b/src/effects/SkLightingImageFilter.cpp
index ddb31936ca..48f16b3ff8 100644
--- a/src/effects/SkLightingImageFilter.cpp
+++ b/src/effects/SkLightingImageFilter.cpp
@@ -14,11 +14,11 @@
#include "SkTypes.h"
#if SK_SUPPORT_GPU
-#include "GrProgramStageFactory.h"
#include "effects/GrSingleTextureEffect.h"
-#include "gl/GrGLProgramStage.h"
-#include "gl/GrGLTexture.h"
-#include "GrCustomStage.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
+#include "GrEffect.h"
+#include "GrTBackendEffectFactory.h"
class GrGLDiffuseLightingEffect;
class GrGLSpecularLightingEffect;
@@ -42,14 +42,7 @@ void setUniformPoint3(const GrGLUniformManager& uman, UniformHandle uni, const S
}
void setUniformNormal3(const GrGLUniformManager& uman, UniformHandle uni, const SkPoint3& point) {
- setUniformPoint3(uman, uni, SkPoint3(point.fX, -point.fY, point.fZ));
-}
-
-void setUniformPoint3FlipY(const GrGLUniformManager& uman,
- UniformHandle uni,
- const SkPoint3& point,
- int height) {
- setUniformPoint3(uman, uni, SkPoint3(point.fX, height-point.fY, point.fZ));
+ setUniformPoint3(uman, uni, SkPoint3(point.fX, point.fY, point.fZ));
}
#endif
@@ -271,7 +264,7 @@ public:
SkScalar kd, SkImageFilter* input);
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkDiffuseLightingImageFilter)
- virtual bool asNewCustomStage(GrCustomStage** stage, GrTexture*) const SK_OVERRIDE;
+ virtual bool asNewEffect(GrEffect** effect, GrTexture*) const SK_OVERRIDE;
SkScalar kd() const { return fKD; }
protected:
@@ -291,7 +284,7 @@ public:
SkSpecularLightingImageFilter(SkLight* light, SkScalar surfaceScale, SkScalar ks, SkScalar shininess, SkImageFilter* input);
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkSpecularLightingImageFilter)
- virtual bool asNewCustomStage(GrCustomStage** stage, GrTexture*) const SK_OVERRIDE;
+ virtual bool asNewEffect(GrEffect** effect, GrTexture*) const SK_OVERRIDE;
SkScalar ks() const { return fKS; }
SkScalar shininess() const { return fShininess; }
@@ -314,7 +307,7 @@ public:
GrLightingEffect(GrTexture* texture, const SkLight* light, SkScalar surfaceScale);
virtual ~GrLightingEffect();
- virtual bool isEqual(const GrCustomStage&) const SK_OVERRIDE;
+ virtual bool isEqual(const GrEffect&) const SK_OVERRIDE;
const SkLight* light() const { return fLight; }
SkScalar surfaceScale() const { return fSurfaceScale; }
@@ -333,13 +326,13 @@ public:
static const char* Name() { return "DiffuseLighting"; }
- typedef GrGLDiffuseLightingEffect GLProgramStage;
+ typedef GrGLDiffuseLightingEffect GLEffect;
- virtual const GrProgramStageFactory& getFactory() const SK_OVERRIDE;
- virtual bool isEqual(const GrCustomStage&) const SK_OVERRIDE;
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+ virtual bool isEqual(const GrEffect&) const SK_OVERRIDE;
SkScalar kd() const { return fKD; }
private:
- GR_DECLARE_CUSTOM_STAGE_TEST;
+ GR_DECLARE_EFFECT_TEST;
typedef GrLightingEffect INHERITED;
SkScalar fKD;
};
@@ -354,15 +347,15 @@ public:
static const char* Name() { return "SpecularLighting"; }
- typedef GrGLSpecularLightingEffect GLProgramStage;
+ typedef GrGLSpecularLightingEffect GLEffect;
- virtual const GrProgramStageFactory& getFactory() const SK_OVERRIDE;
- virtual bool isEqual(const GrCustomStage&) const SK_OVERRIDE;
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+ virtual bool isEqual(const GrEffect&) const SK_OVERRIDE;
SkScalar ks() const { return fKS; }
SkScalar shininess() const { return fShininess; }
private:
- GR_DECLARE_CUSTOM_STAGE_TEST;
+ GR_DECLARE_EFFECT_TEST;
typedef GrLightingEffect INHERITED;
SkScalar fKS;
SkScalar fShininess;
@@ -373,21 +366,39 @@ private:
class GrGLLight {
public:
virtual ~GrGLLight() {}
- virtual void setupVariables(GrGLShaderBuilder* builder);
- virtual void emitVS(SkString* out) const {}
- virtual void emitFuncs(GrGLShaderBuilder* builder) {}
- virtual void emitSurfaceToLight(const GrGLShaderBuilder*,
- SkString* out,
- const char* z) const = 0;
- virtual void emitLightColor(GrGLShaderBuilder*,
- const char *surfaceToLight) const;
- virtual void setData(const GrGLUniformManager&, const GrRenderTarget* rt, const SkLight* light) const;
-private:
- typedef SkRefCnt INHERITED;
+ /**
+ * This is called by GrGLLightingEffect::emitCode() before either of the two virtual functions
+ * below. It adds a vec3f uniform visible in the FS that represents the constant light color.
+ */
+ void emitLightColorUniform(GrGLShaderBuilder*);
+
+ /**
+ * These two functions are called from GrGLLightingEffect's emitCode() function.
+ * emitSurfaceToLight places an expression in param out that is the vector from the surface to
+ * the light. The expression will be used in the FS. emitLightColor writes an expression into
+ * the FS that is the color of the light. Either function may add functions and/or uniforms to
+ * the FS. The default of emitLightColor appends the name of the constant light color uniform
+ * and so this function only needs to be overridden if the light color varies spatially.
+ */
+ virtual void emitSurfaceToLight(GrGLShaderBuilder*, SkString* out, const char* z) = 0;
+ virtual void emitLightColor(GrGLShaderBuilder*, const char *surfaceToLight);
+
+ // This is called from GrGLLightingEffect's setData(). Subclasses of GrGLLight must call
+ // INHERITED::setData().
+ virtual void setData(const GrGLUniformManager&, const SkLight* light) const;
protected:
+ /**
+ * Gets the constant light color uniform. Subclasses can use this in their emitLightColor
+ * function.
+ */
+ UniformHandle lightColorUni() const { return fColorUni; }
+
+private:
UniformHandle fColorUni;
+
+ typedef SkRefCnt INHERITED;
};
///////////////////////////////////////////////////////////////////////////////
@@ -395,11 +406,8 @@ protected:
class GrGLDistantLight : public GrGLLight {
public:
virtual ~GrGLDistantLight() {}
- virtual void setupVariables(GrGLShaderBuilder* builder) SK_OVERRIDE;
- virtual void setData(const GrGLUniformManager&, const GrRenderTarget* rt, const SkLight* light) const SK_OVERRIDE;
- virtual void emitSurfaceToLight(const GrGLShaderBuilder*,
- SkString* out,
- const char* z) const SK_OVERRIDE;
+ virtual void setData(const GrGLUniformManager&, const SkLight* light) const SK_OVERRIDE;
+ virtual void emitSurfaceToLight(GrGLShaderBuilder*, SkString* out, const char* z) SK_OVERRIDE;
private:
typedef GrGLLight INHERITED;
UniformHandle fDirectionUni;
@@ -410,12 +418,8 @@ private:
class GrGLPointLight : public GrGLLight {
public:
virtual ~GrGLPointLight() {}
- virtual void setupVariables(GrGLShaderBuilder* builder) SK_OVERRIDE;
- virtual void setData(const GrGLUniformManager&, const GrRenderTarget* rt, const SkLight* light) const SK_OVERRIDE;
- virtual void emitVS(SkString* out) const SK_OVERRIDE;
- virtual void emitSurfaceToLight(const GrGLShaderBuilder*,
- SkString* out,
- const char* z) const SK_OVERRIDE;
+ virtual void setData(const GrGLUniformManager&, const SkLight* light) const SK_OVERRIDE;
+ virtual void emitSurfaceToLight(GrGLShaderBuilder*, SkString* out, const char* z) SK_OVERRIDE;
private:
typedef GrGLLight INHERITED;
SkPoint3 fLocation;
@@ -427,16 +431,9 @@ private:
class GrGLSpotLight : public GrGLLight {
public:
virtual ~GrGLSpotLight() {}
- virtual void setupVariables(GrGLShaderBuilder* builder) SK_OVERRIDE;
- virtual void setData(const GrGLUniformManager&, const GrRenderTarget* rt, const SkLight* light) const SK_OVERRIDE;
- virtual void emitVS(SkString* out) const SK_OVERRIDE;
- virtual void emitFuncs(GrGLShaderBuilder* builder);
- virtual void emitSurfaceToLight(const GrGLShaderBuilder* builder,
- SkString* out,
- const char* z) const SK_OVERRIDE;
- virtual void emitLightColor(GrGLShaderBuilder*,
- const char *surfaceToLight) const SK_OVERRIDE;
-
+ virtual void setData(const GrGLUniformManager&, const SkLight* light) const SK_OVERRIDE;
+ virtual void emitSurfaceToLight(GrGLShaderBuilder*, SkString* out, const char* z) SK_OVERRIDE;
+ virtual void emitLightColor(GrGLShaderBuilder*, const char *surfaceToLight) SK_OVERRIDE;
private:
typedef GrGLLight INHERITED;
@@ -828,12 +825,12 @@ bool SkDiffuseLightingImageFilter::onFilterImage(Proxy*,
return true;
}
-bool SkDiffuseLightingImageFilter::asNewCustomStage(GrCustomStage** stage,
- GrTexture* texture) const {
+bool SkDiffuseLightingImageFilter::asNewEffect(GrEffect** effect,
+ GrTexture* texture) const {
#if SK_SUPPORT_GPU
- if (stage) {
+ if (effect) {
SkScalar scale = SkScalarMul(surfaceScale(), SkIntToScalar(255));
- *stage = SkNEW_ARGS(GrDiffuseLightingEffect, (texture, light(), scale, kd()));
+ *effect = SkNEW_ARGS(GrDiffuseLightingEffect, (texture, light(), scale, kd()));
}
return true;
#else
@@ -897,12 +894,12 @@ bool SkSpecularLightingImageFilter::onFilterImage(Proxy*,
return true;
}
-bool SkSpecularLightingImageFilter::asNewCustomStage(GrCustomStage** stage,
- GrTexture* texture) const {
+bool SkSpecularLightingImageFilter::asNewEffect(GrEffect** effect,
+ GrTexture* texture) const {
#if SK_SUPPORT_GPU
- if (stage) {
+ if (effect) {
SkScalar scale = SkScalarMul(surfaceScale(), SkIntToScalar(255));
- *stage = SkNEW_ARGS(GrSpecularLightingEffect, (texture, light(), scale, ks(), shininess()));
+ *effect = SkNEW_ARGS(GrSpecularLightingEffect, (texture, light(), scale, ks(), shininess()));
}
return true;
#else
@@ -946,49 +943,47 @@ SkLight* create_random_light(SkRandom* random) {
}
-class GrGLLightingEffect : public GrGLProgramStage {
+class GrGLLightingEffect : public GrGLEffect {
public:
- GrGLLightingEffect(const GrProgramStageFactory& factory,
- const GrCustomStage& stage);
+ GrGLLightingEffect(const GrBackendEffectFactory& factory,
+ const GrEffect& effect);
virtual ~GrGLLightingEffect();
- virtual void setupVariables(GrGLShaderBuilder* builder) SK_OVERRIDE;
- virtual void emitVS(GrGLShaderBuilder* builder,
- const char* vertexCoords) SK_OVERRIDE;
- virtual void emitFS(GrGLShaderBuilder* builder,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray&) SK_OVERRIDE;
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrEffectStage&,
+ EffectKey,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
- virtual void emitLightFunc(GrGLShaderBuilder*, SkString* funcName) = 0;
+ static inline EffectKey GenKey(const GrEffectStage&, const GrGLCaps&);
- static inline StageKey GenKey(const GrCustomStage& s, const GrGLCaps& caps);
+ /**
+ * Subclasses of GrGLLightingEffect must call INHERITED::setData();
+ */
+ virtual void setData(const GrGLUniformManager&, const GrEffectStage&) SK_OVERRIDE;
- virtual void setData(const GrGLUniformManager&,
- const GrCustomStage&,
- const GrRenderTarget*,
- int stageNum) SK_OVERRIDE;
+protected:
+ virtual void emitLightFunc(GrGLShaderBuilder*, SkString* funcName) = 0;
private:
- typedef GrGLProgramStage INHERITED;
+ typedef GrGLEffect INHERITED;
- UniformHandle fImageIncrementUni;
- UniformHandle fSurfaceScaleUni;
- GrGLLight* fLight;
+ UniformHandle fImageIncrementUni;
+ UniformHandle fSurfaceScaleUni;
+ GrGLLight* fLight;
+ GrGLEffectMatrix fEffectMatrix;
};
///////////////////////////////////////////////////////////////////////////////
class GrGLDiffuseLightingEffect : public GrGLLightingEffect {
public:
- GrGLDiffuseLightingEffect(const GrProgramStageFactory& factory,
- const GrCustomStage& stage);
- virtual void setupVariables(GrGLShaderBuilder* builder) SK_OVERRIDE;
+ GrGLDiffuseLightingEffect(const GrBackendEffectFactory& factory,
+ const GrEffect& effect);
virtual void emitLightFunc(GrGLShaderBuilder*, SkString* funcName) SK_OVERRIDE;
- virtual void setData(const GrGLUniformManager&,
- const GrCustomStage&,
- const GrRenderTarget*,
- int stageNum) SK_OVERRIDE;
+ virtual void setData(const GrGLUniformManager&, const GrEffectStage&) SK_OVERRIDE;
private:
typedef GrGLLightingEffect INHERITED;
@@ -1000,14 +995,10 @@ private:
class GrGLSpecularLightingEffect : public GrGLLightingEffect {
public:
- GrGLSpecularLightingEffect(const GrProgramStageFactory& factory,
- const GrCustomStage& stage);
- virtual void setupVariables(GrGLShaderBuilder* builder) SK_OVERRIDE;
+ GrGLSpecularLightingEffect(const GrBackendEffectFactory& factory,
+ const GrEffect& effect);
virtual void emitLightFunc(GrGLShaderBuilder*, SkString* funcName) SK_OVERRIDE;
- virtual void setData(const GrGLUniformManager&,
- const GrCustomStage&,
- const GrRenderTarget*,
- int stageNum) SK_OVERRIDE;
+ virtual void setData(const GrGLUniformManager&, const GrEffectStage&) SK_OVERRIDE;
private:
typedef GrGLLightingEffect INHERITED;
@@ -1019,7 +1010,7 @@ private:
///////////////////////////////////////////////////////////////////////////////
GrLightingEffect::GrLightingEffect(GrTexture* texture, const SkLight* light, SkScalar surfaceScale)
- : GrSingleTextureEffect(texture)
+ : INHERITED(texture, MakeDivByTextureWHMatrix(texture))
, fLight(light)
, fSurfaceScale(surfaceScale) {
fLight->ref();
@@ -1029,7 +1020,7 @@ GrLightingEffect::~GrLightingEffect() {
fLight->unref();
}
-bool GrLightingEffect::isEqual(const GrCustomStage& sBase) const {
+bool GrLightingEffect::isEqual(const GrEffect& sBase) const {
const GrLightingEffect& s =
static_cast<const GrLightingEffect&>(sBase);
return INHERITED::isEqual(sBase) &&
@@ -1043,38 +1034,38 @@ GrDiffuseLightingEffect::GrDiffuseLightingEffect(GrTexture* texture, const SkLig
: INHERITED(texture, light, surfaceScale), fKD(kd) {
}
-const GrProgramStageFactory& GrDiffuseLightingEffect::getFactory() const {
- return GrTProgramStageFactory<GrDiffuseLightingEffect>::getInstance();
+const GrBackendEffectFactory& GrDiffuseLightingEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrDiffuseLightingEffect>::getInstance();
}
-bool GrDiffuseLightingEffect::isEqual(const GrCustomStage& sBase) const {
+bool GrDiffuseLightingEffect::isEqual(const GrEffect& sBase) const {
const GrDiffuseLightingEffect& s =
static_cast<const GrDiffuseLightingEffect&>(sBase);
return INHERITED::isEqual(sBase) &&
this->kd() == s.kd();
}
-GR_DEFINE_CUSTOM_STAGE_TEST(GrDiffuseLightingEffect);
+GR_DEFINE_EFFECT_TEST(GrDiffuseLightingEffect);
-GrCustomStage* GrDiffuseLightingEffect::TestCreate(SkRandom* random,
- GrContext* context,
- GrTexture* textures[]) {
+GrEffect* GrDiffuseLightingEffect::TestCreate(SkRandom* random,
+ GrContext* context,
+ GrTexture* textures[]) {
SkScalar surfaceScale = random->nextSScalar1();
SkScalar kd = random->nextUScalar1();
SkAutoTUnref<SkLight> light(create_random_light(random));
- return SkNEW_ARGS(GrDiffuseLightingEffect, (textures[GrCustomStageUnitTest::kAlphaTextureIdx],
+ return SkNEW_ARGS(GrDiffuseLightingEffect, (textures[GrEffectUnitTest::kAlphaTextureIdx],
light, surfaceScale, kd));
}
///////////////////////////////////////////////////////////////////////////////
-GrGLLightingEffect::GrGLLightingEffect(const GrProgramStageFactory& factory,
- const GrCustomStage& stage)
- : GrGLProgramStage(factory)
+GrGLLightingEffect::GrGLLightingEffect(const GrBackendEffectFactory& factory,
+ const GrEffect& effect)
+ : INHERITED(factory)
, fImageIncrementUni(kInvalidUniformHandle)
, fSurfaceScaleUni(kInvalidUniformHandle) {
- const GrLightingEffect& m = static_cast<const GrLightingEffect&>(stage);
+ const GrLightingEffect& m = static_cast<const GrLightingEffect&>(effect);
fLight = m.light()->createGLLight();
}
@@ -1082,27 +1073,24 @@ GrGLLightingEffect::~GrGLLightingEffect() {
delete fLight;
}
-void GrGLLightingEffect::setupVariables(GrGLShaderBuilder* builder) {
+void GrGLLightingEffect::emitCode(GrGLShaderBuilder* builder,
+ const GrEffectStage&,
+ EffectKey key,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ const char* coords;
+ fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, vertexCoords, &coords);
+
fImageIncrementUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
kVec2f_GrSLType,
"ImageIncrement");
fSurfaceScaleUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
kFloat_GrSLType,
"SurfaceScale");
- fLight->setupVariables(builder);
-}
-
-void GrGLLightingEffect::emitVS(GrGLShaderBuilder* builder,
- const char* vertexCoords) {
- fLight->emitVS(&builder->fVSCode);
-}
-
-void GrGLLightingEffect::emitFS(GrGLShaderBuilder* builder,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray& samplers) {
+ fLight->emitLightColorUniform(builder);
SkString* code = &builder->fFSCode;
- fLight->emitFuncs(builder);
SkString lightFunc;
this->emitLightFunc(builder, &lightFunc);
static const GrGLShaderVar gSobelArgs[] = {
@@ -1133,7 +1121,7 @@ void GrGLLightingEffect::emitFS(GrGLShaderBuilder* builder,
"pointToNormal",
SK_ARRAY_COUNT(gPointToNormalArgs),
gPointToNormalArgs,
- "\treturn normalize(vec3(-x * scale, -y * scale, 1));\n",
+ "\treturn normalize(vec3(-x * scale, y * scale, 1));\n",
&pointToNormalName);
static const GrGLShaderVar gInteriorNormalArgs[] = {
@@ -1156,7 +1144,7 @@ void GrGLLightingEffect::emitFS(GrGLShaderBuilder* builder,
interiorNormalBody.c_str(),
&interiorNormalName);
- code->appendf("\t\tvec2 coord = %s;\n", builder->defaultTexCoordsName());
+ code->appendf("\t\tvec2 coord = %s;\n", coords);
code->appendf("\t\tfloat m[9];\n");
const char* imgInc = builder->getUniformCStr(fImageIncrementUni);
@@ -1184,41 +1172,47 @@ void GrGLLightingEffect::emitFS(GrGLShaderBuilder* builder,
GrGLSLMulVarBy4f(code, 2, outputColor, inputColor);
}
-GrGLProgramStage::StageKey GrGLLightingEffect::GenKey(const GrCustomStage& s,
- const GrGLCaps& caps) {
- return static_cast<const GrLightingEffect&>(s).light()->type();
+GrGLEffect::EffectKey GrGLLightingEffect::GenKey(const GrEffectStage& s,
+ const GrGLCaps& caps) {
+ const GrLightingEffect& effect = static_cast<const GrLightingEffect&>(*s.getEffect());
+ EffectKey key = static_cast<const GrLightingEffect&>(*s.getEffect()).light()->type();
+ key <<= GrGLEffectMatrix::kKeyBits;
+ EffectKey matrixKey = GrGLEffectMatrix::GenKey(effect.getMatrix(),
+ s.getCoordChangeMatrix(),
+ effect.texture(0));
+ return key | matrixKey;
}
-void GrGLLightingEffect::setData(const GrGLUniformManager& uman,
- const GrCustomStage& data,
- const GrRenderTarget* rt,
- int stageNum) {
- const GrLightingEffect& effect =
- static_cast<const GrLightingEffect&>(data);
- GrGLTexture* texture = static_cast<GrGLTexture*>(data.texture(0));
- float ySign = texture->orientation() == GrGLTexture::kTopDown_Orientation ? -1.0f : 1.0f;
+void GrGLLightingEffect::setData(const GrGLUniformManager& uman, const GrEffectStage& stage) {
+ const GrLightingEffect& effect =static_cast<const GrLightingEffect&>(*stage.getEffect());
+ GrTexture* texture = effect.texture(0);
+ float ySign = texture->origin() == GrSurface::kTopLeft_Origin ? -1.0f : 1.0f;
uman.set2f(fImageIncrementUni, 1.0f / texture->width(), ySign / texture->height());
uman.set1f(fSurfaceScaleUni, effect.surfaceScale());
- fLight->setData(uman, rt, effect.light());
+ fLight->setData(uman, effect.light());
+ fEffectMatrix.setData(uman,
+ effect.getMatrix(),
+ stage.getCoordChangeMatrix(),
+ effect.texture(0));
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
-GrGLDiffuseLightingEffect::GrGLDiffuseLightingEffect(const GrProgramStageFactory& factory,
- const GrCustomStage& stage)
- : INHERITED(factory, stage)
+GrGLDiffuseLightingEffect::GrGLDiffuseLightingEffect(const GrBackendEffectFactory& factory,
+ const GrEffect& effect)
+ : INHERITED(factory, effect)
, fKDUni(kInvalidUniformHandle) {
}
-void GrGLDiffuseLightingEffect::setupVariables(GrGLShaderBuilder* builder) {
- INHERITED::setupVariables(builder);
- fKDUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType, kFloat_GrSLType, "KD");
-}
-
void GrGLDiffuseLightingEffect::emitLightFunc(GrGLShaderBuilder* builder, SkString* funcName) {
- const char* kd = builder->getUniformCStr(fKDUni);
+ const char* kd;
+ fKDUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType,
+ "KD",
+ &kd);
+
static const GrGLShaderVar gLightArgs[] = {
GrGLShaderVar("normal", kVec3f_GrSLType),
GrGLShaderVar("surfaceToLight", kVec3f_GrSLType),
@@ -1237,12 +1231,10 @@ void GrGLDiffuseLightingEffect::emitLightFunc(GrGLShaderBuilder* builder, SkStri
}
void GrGLDiffuseLightingEffect::setData(const GrGLUniformManager& uman,
- const GrCustomStage& data,
- const GrRenderTarget* rt,
- int stageNum) {
- INHERITED::setData(uman, data, rt, stageNum);
+ const GrEffectStage& stage) {
+ INHERITED::setData(uman, stage);
const GrDiffuseLightingEffect& effect =
- static_cast<const GrDiffuseLightingEffect&>(data);
+ static_cast<const GrDiffuseLightingEffect&>(*stage.getEffect());
uman.set1f(fKDUni, effect.kd());
}
@@ -1254,11 +1246,11 @@ GrSpecularLightingEffect::GrSpecularLightingEffect(GrTexture* texture, const SkL
fShininess(shininess) {
}
-const GrProgramStageFactory& GrSpecularLightingEffect::getFactory() const {
- return GrTProgramStageFactory<GrSpecularLightingEffect>::getInstance();
+const GrBackendEffectFactory& GrSpecularLightingEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrSpecularLightingEffect>::getInstance();
}
-bool GrSpecularLightingEffect::isEqual(const GrCustomStage& sBase) const {
+bool GrSpecularLightingEffect::isEqual(const GrEffect& sBase) const {
const GrSpecularLightingEffect& s =
static_cast<const GrSpecularLightingEffect&>(sBase);
return INHERITED::isEqual(sBase) &&
@@ -1266,39 +1258,36 @@ bool GrSpecularLightingEffect::isEqual(const GrCustomStage& sBase) const {
this->shininess() == s.shininess();
}
-GR_DEFINE_CUSTOM_STAGE_TEST(GrSpecularLightingEffect);
+GR_DEFINE_EFFECT_TEST(GrSpecularLightingEffect);
-GrCustomStage* GrSpecularLightingEffect::TestCreate(SkRandom* random,
- GrContext* context,
- GrTexture* textures[]) {
+GrEffect* GrSpecularLightingEffect::TestCreate(SkRandom* random,
+ GrContext* context,
+ GrTexture* textures[]) {
SkScalar surfaceScale = random->nextSScalar1();
SkScalar ks = random->nextUScalar1();
SkScalar shininess = random->nextUScalar1();
SkAutoTUnref<SkLight> light(create_random_light(random));
- return SkNEW_ARGS(GrSpecularLightingEffect, (textures[GrCustomStageUnitTest::kAlphaTextureIdx],
+ return SkNEW_ARGS(GrSpecularLightingEffect, (textures[GrEffectUnitTest::kAlphaTextureIdx],
light, surfaceScale, ks, shininess));
}
///////////////////////////////////////////////////////////////////////////////
-GrGLSpecularLightingEffect::GrGLSpecularLightingEffect(const GrProgramStageFactory& factory,
- const GrCustomStage& stage)
- : GrGLLightingEffect(factory, stage)
+GrGLSpecularLightingEffect::GrGLSpecularLightingEffect(const GrBackendEffectFactory& factory,
+ const GrEffect& effect)
+ : GrGLLightingEffect(factory, effect)
, fKSUni(kInvalidUniformHandle)
, fShininessUni(kInvalidUniformHandle) {
}
-void GrGLSpecularLightingEffect::setupVariables(GrGLShaderBuilder* builder) {
- INHERITED::setupVariables(builder);
+void GrGLSpecularLightingEffect::emitLightFunc(GrGLShaderBuilder* builder, SkString* funcName) {
+ const char* ks;
+ const char* shininess;
+
fKSUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
- kFloat_GrSLType, "KS");
+ kFloat_GrSLType, "KS", &ks);
fShininessUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
- kFloat_GrSLType, "Shininess");
-}
-
-void GrGLSpecularLightingEffect::emitLightFunc(GrGLShaderBuilder* builder, SkString* funcName) {
- const char* ks = builder->getUniformCStr(fKSUni);
- const char* shininess = builder->getUniformCStr(fShininessUni);
+ kFloat_GrSLType, "Shininess", &shininess);
static const GrGLShaderVar gLightArgs[] = {
GrGLShaderVar("normal", kVec3f_GrSLType),
@@ -1308,7 +1297,8 @@ void GrGLSpecularLightingEffect::emitLightFunc(GrGLShaderBuilder* builder, SkStr
SkString lightBody;
lightBody.appendf("\tvec3 halfDir = vec3(normalize(surfaceToLight + vec3(0, 0, 1)));\n");
lightBody.appendf("\tfloat colorScale = %s * pow(dot(normal, halfDir), %s);\n", ks, shininess);
- lightBody.appendf("\treturn vec4(lightColor * clamp(colorScale, 0.0, 1.0), 1.0);\n");
+ lightBody.appendf("\tvec3 color = lightColor * clamp(colorScale, 0.0, 1.0);\n");
+ lightBody.appendf("\treturn vec4(color, max(max(color.r, color.g), color.b));\n");
builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType,
kVec4f_GrSLType,
"light",
@@ -1319,110 +1309,75 @@ void GrGLSpecularLightingEffect::emitLightFunc(GrGLShaderBuilder* builder, SkStr
}
void GrGLSpecularLightingEffect::setData(const GrGLUniformManager& uman,
- const GrCustomStage& data,
- const GrRenderTarget* rt,
- int stageNum) {
- INHERITED::setData(uman, data, rt, stageNum);
- const GrSpecularLightingEffect& effect = static_cast<const GrSpecularLightingEffect&>(data);
+ const GrEffectStage& stage) {
+ INHERITED::setData(uman, stage);
+ const GrSpecularLightingEffect& effect =
+ static_cast<const GrSpecularLightingEffect&>(*stage.getEffect());
uman.set1f(fKSUni, effect.ks());
uman.set1f(fShininessUni, effect.shininess());
}
///////////////////////////////////////////////////////////////////////////////
-
-void GrGLLight::emitLightColor(GrGLShaderBuilder* builder,
- const char *surfaceToLight) const {
- const char* color = builder->getUniformCStr(fColorUni);
- builder->fFSCode.append(color);
-}
-
-void GrGLLight::setupVariables(GrGLShaderBuilder* builder) {
+void GrGLLight::emitLightColorUniform(GrGLShaderBuilder* builder) {
fColorUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
kVec3f_GrSLType, "LightColor");
}
+void GrGLLight::emitLightColor(GrGLShaderBuilder* builder,
+ const char *surfaceToLight) {
+ builder->fFSCode.append(builder->getUniformCStr(this->lightColorUni()));
+}
+
void GrGLLight::setData(const GrGLUniformManager& uman,
- const GrRenderTarget* rt,
const SkLight* light) const {
setUniformPoint3(uman, fColorUni, light->color() * SkScalarInvert(SkIntToScalar(255)));
}
///////////////////////////////////////////////////////////////////////////////
-void GrGLDistantLight::setupVariables(GrGLShaderBuilder* builder) {
- INHERITED::setupVariables(builder);
- fDirectionUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType, kVec3f_GrSLType,
- "LightDirection");
-}
-
-void GrGLDistantLight::setData(const GrGLUniformManager& uman,
- const GrRenderTarget* rt,
- const SkLight* light) const {
- INHERITED::setData(uman, rt, light);
+void GrGLDistantLight::setData(const GrGLUniformManager& uman, const SkLight* light) const {
+ INHERITED::setData(uman, light);
SkASSERT(light->type() == SkLight::kDistant_LightType);
const SkDistantLight* distantLight = static_cast<const SkDistantLight*>(light);
setUniformNormal3(uman, fDirectionUni, distantLight->direction());
}
-void GrGLDistantLight::emitSurfaceToLight(const GrGLShaderBuilder* builder,
+void GrGLDistantLight::emitSurfaceToLight(GrGLShaderBuilder* builder,
SkString* out,
- const char* z) const {
- const char* dir = builder->getUniformCStr(fDirectionUni);
+ const char* z) {
+ const char* dir;
+ fDirectionUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType, kVec3f_GrSLType,
+ "LightDirection", &dir);
out->append(dir);
}
///////////////////////////////////////////////////////////////////////////////
-void GrGLPointLight::setupVariables(GrGLShaderBuilder* builder) {
- INHERITED::setupVariables(builder);
- fLocationUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType, kVec3f_GrSLType,
- "LightLocation");
-}
-
void GrGLPointLight::setData(const GrGLUniformManager& uman,
- const GrRenderTarget* rt,
const SkLight* light) const {
- INHERITED::setData(uman, rt, light);
+ INHERITED::setData(uman, light);
SkASSERT(light->type() == SkLight::kPoint_LightType);
const SkPointLight* pointLight = static_cast<const SkPointLight*>(light);
- setUniformPoint3FlipY(uman, fLocationUni, pointLight->location(), rt->height());
-}
-
-void GrGLPointLight::emitVS(SkString* out) const {
+ setUniformPoint3(uman, fLocationUni, pointLight->location());
}
-void GrGLPointLight::emitSurfaceToLight(const GrGLShaderBuilder* builder,
+void GrGLPointLight::emitSurfaceToLight(GrGLShaderBuilder* builder,
SkString* out,
- const char* z) const {
- const char* loc = builder->getUniformCStr(fLocationUni);
- out->appendf("normalize(%s - vec3(gl_FragCoord.xy, %s))", loc, z);
+ const char* z) {
+ const char* loc;
+ fLocationUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType, kVec3f_GrSLType,
+ "LightLocation", &loc);
+ out->appendf("normalize(%s - vec3(%s.xy, %s))", loc, builder->fragmentPosition(), z);
}
///////////////////////////////////////////////////////////////////////////////
-void GrGLSpotLight::setupVariables(GrGLShaderBuilder* builder) {
- INHERITED::setupVariables(builder);
- fLocationUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
- kVec3f_GrSLType, "LightLocation");
- fExponentUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
- kFloat_GrSLType, "Exponent");
- fCosInnerConeAngleUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
- kFloat_GrSLType, "CosInnerConeAngle");
- fCosOuterConeAngleUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
- kFloat_GrSLType, "CosOuterConeAngle");
- fConeScaleUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
- kFloat_GrSLType, "ConeScale");
- fSUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
- kVec3f_GrSLType, "S");
-}
-
void GrGLSpotLight::setData(const GrGLUniformManager& uman,
- const GrRenderTarget* rt,
const SkLight* light) const {
- INHERITED::setData(uman, rt, light);
+ INHERITED::setData(uman, light);
SkASSERT(light->type() == SkLight::kSpot_LightType);
const SkSpotLight* spotLight = static_cast<const SkSpotLight *>(light);
- setUniformPoint3FlipY(uman, fLocationUni, spotLight->location(), rt->height());
+ setUniformPoint3(uman, fLocationUni, spotLight->location());
uman.set1f(fExponentUni, spotLight->specularExponent());
uman.set1f(fCosInnerConeAngleUni, spotLight->cosInnerConeAngle());
uman.set1f(fCosOuterConeAngleUni, spotLight->cosOuterConeAngle());
@@ -1430,16 +1385,36 @@ void GrGLSpotLight::setData(const GrGLUniformManager& uman,
setUniformNormal3(uman, fSUni, spotLight->s());
}
-void GrGLSpotLight::emitVS(SkString* out) const {
+void GrGLSpotLight::emitSurfaceToLight(GrGLShaderBuilder* builder,
+ SkString* out,
+ const char* z) {
+ const char* location;
+ fLocationUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec3f_GrSLType, "LightLocation", &location);
+ out->appendf("normalize(%s - vec3(%s.xy, %s))", location, builder->fragmentPosition(), z);
}
-void GrGLSpotLight::emitFuncs(GrGLShaderBuilder* builder) {
- const char* exponent = builder->getUniformCStr(fExponentUni);
- const char* cosInner = builder->getUniformCStr(fCosInnerConeAngleUni);
- const char* cosOuter = builder->getUniformCStr(fCosOuterConeAngleUni);
- const char* coneScale = builder->getUniformCStr(fConeScaleUni);
- const char* s = builder->getUniformCStr(fSUni);
- const char* color = builder->getUniformCStr(fColorUni);
+void GrGLSpotLight::emitLightColor(GrGLShaderBuilder* builder,
+ const char *surfaceToLight) {
+
+ const char* color = builder->getUniformCStr(this->lightColorUni()); // created by parent class.
+
+ const char* exponent;
+ const char* cosInner;
+ const char* cosOuter;
+ const char* coneScale;
+ const char* s;
+ fExponentUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "Exponent", &exponent);
+ fCosInnerConeAngleUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "CosInnerConeAngle", &cosInner);
+ fCosOuterConeAngleUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "CosOuterConeAngle", &cosOuter);
+ fConeScaleUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "ConeScale", &coneScale);
+ fSUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec3f_GrSLType, "S", &s);
+
static const GrGLShaderVar gLightColorArgs[] = {
GrGLShaderVar("surfaceToLight", kVec3f_GrSLType)
};
@@ -1461,17 +1436,7 @@ void GrGLSpotLight::emitFuncs(GrGLShaderBuilder* builder) {
gLightColorArgs,
lightColorBody.c_str(),
&fLightColorFunc);
-}
-void GrGLSpotLight::emitSurfaceToLight(const GrGLShaderBuilder* builder,
- SkString* out,
- const char* z) const {
- const char* location= builder->getUniformCStr(fLocationUni);
- out->appendf("normalize(%s - vec3(gl_FragCoord.xy, %s))", location, z);
-}
-
-void GrGLSpotLight::emitLightColor(GrGLShaderBuilder* builder,
- const char *surfaceToLight) const {
builder->fFSCode.appendf("%s(%s)", fLightColorFunc.c_str(), surfaceToLight);
}
diff --git a/src/effects/SkMagnifierImageFilter.cpp b/src/effects/SkMagnifierImageFilter.cpp
index 2b9395dcf5..546935f658 100644
--- a/src/effects/SkMagnifierImageFilter.cpp
+++ b/src/effects/SkMagnifierImageFilter.cpp
@@ -13,10 +13,11 @@
////////////////////////////////////////////////////////////////////////////////
#if SK_SUPPORT_GPU
#include "effects/GrSingleTextureEffect.h"
-#include "gl/GrGLProgramStage.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
#include "gl/GrGLSL.h"
#include "gl/GrGLTexture.h"
-#include "GrProgramStageFactory.h"
+#include "GrTBackendEffectFactory.h"
class GrGLMagnifierEffect;
@@ -30,7 +31,7 @@ public:
float yZoom,
float xInset,
float yInset)
- : GrSingleTextureEffect(texture)
+ : GrSingleTextureEffect(texture, MakeDivByTextureWHMatrix(texture))
, fXOffset(xOffset)
, fYOffset(yOffset)
, fXZoom(xZoom)
@@ -42,8 +43,8 @@ public:
static const char* Name() { return "Magnifier"; }
- virtual const GrProgramStageFactory& getFactory() const SK_OVERRIDE;
- virtual bool isEqual(const GrCustomStage&) const SK_OVERRIDE;
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+ virtual bool isEqual(const GrEffect&) const SK_OVERRIDE;
float x_offset() const { return fXOffset; }
float y_offset() const { return fYOffset; }
@@ -52,10 +53,10 @@ public:
float x_inset() const { return fXInset; }
float y_inset() const { return fYInset; }
- typedef GrGLMagnifierEffect GLProgramStage;
+ typedef GrGLMagnifierEffect GLEffect;
private:
- GR_DECLARE_CUSTOM_STAGE_TEST;
+ GR_DECLARE_EFFECT_TEST;
float fXOffset;
float fYOffset;
@@ -70,78 +71,75 @@ private:
// For brevity
typedef GrGLUniformManager::UniformHandle UniformHandle;
-class GrGLMagnifierEffect : public GrGLProgramStage {
+class GrGLMagnifierEffect : public GrGLEffect {
public:
- GrGLMagnifierEffect(const GrProgramStageFactory& factory,
- const GrCustomStage& stage);
+ GrGLMagnifierEffect(const GrBackendEffectFactory& factory,
+ const GrEffect& effect);
- virtual void setupVariables(GrGLShaderBuilder* state) SK_OVERRIDE;
- virtual void emitVS(GrGLShaderBuilder* state,
- const char* vertexCoords) SK_OVERRIDE;
- virtual void emitFS(GrGLShaderBuilder* state,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray&) SK_OVERRIDE;
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrEffectStage&,
+ EffectKey,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
- virtual void setData(const GrGLUniformManager& uman,
- const GrCustomStage& data,
- const GrRenderTarget*,
- int stageNum) SK_OVERRIDE;
+ virtual void setData(const GrGLUniformManager& uman, const GrEffectStage& stage) SK_OVERRIDE;
- static inline StageKey GenKey(const GrCustomStage&, const GrGLCaps&);
+ static inline EffectKey GenKey(const GrEffectStage&, const GrGLCaps&);
private:
- UniformHandle fOffsetVar;
- UniformHandle fZoomVar;
- UniformHandle fInsetVar;
+ UniformHandle fOffsetVar;
+ UniformHandle fZoomVar;
+ UniformHandle fInsetVar;
- typedef GrGLProgramStage INHERITED;
+ GrGLEffectMatrix fEffectMatrix;
+
+ typedef GrGLEffect INHERITED;
};
-GrGLMagnifierEffect::GrGLMagnifierEffect(const GrProgramStageFactory& factory,
- const GrCustomStage& stage)
- : GrGLProgramStage(factory)
+GrGLMagnifierEffect::GrGLMagnifierEffect(const GrBackendEffectFactory& factory,
+ const GrEffect& effect)
+ : INHERITED(factory)
, fOffsetVar(GrGLUniformManager::kInvalidUniformHandle)
, fZoomVar(GrGLUniformManager::kInvalidUniformHandle)
, fInsetVar(GrGLUniformManager::kInvalidUniformHandle) {
}
-void GrGLMagnifierEffect::setupVariables(GrGLShaderBuilder* state) {
- fOffsetVar = state->addUniform(
+void GrGLMagnifierEffect::emitCode(GrGLShaderBuilder* builder,
+ const GrEffectStage&,
+ EffectKey key,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ const char* coords;
+ fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, vertexCoords, &coords);
+ fOffsetVar = builder->addUniform(
GrGLShaderBuilder::kFragment_ShaderType |
GrGLShaderBuilder::kVertex_ShaderType,
kVec2f_GrSLType, "uOffset");
- fZoomVar = state->addUniform(
+ fZoomVar = builder->addUniform(
GrGLShaderBuilder::kFragment_ShaderType |
GrGLShaderBuilder::kVertex_ShaderType,
kVec2f_GrSLType, "uZoom");
- fInsetVar = state->addUniform(
+ fInsetVar = builder->addUniform(
GrGLShaderBuilder::kFragment_ShaderType |
GrGLShaderBuilder::kVertex_ShaderType,
kVec2f_GrSLType, "uInset");
-}
-
-void GrGLMagnifierEffect::emitVS(GrGLShaderBuilder* state,
- const char* vertexCoords) {
-}
-void GrGLMagnifierEffect::emitFS(GrGLShaderBuilder* state,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray& samplers) {
- SkString* code = &state->fFSCode;
+ SkString* code = &builder->fFSCode;
- code->appendf("\t\tvec2 coord = %s;\n", state->defaultTexCoordsName());
+ code->appendf("\t\tvec2 coord = %s;\n", coords);
code->appendf("\t\tvec2 zoom_coord = %s + %s / %s;\n",
- state->getUniformCStr(fOffsetVar),
- state->defaultTexCoordsName(),
- state->getUniformCStr(fZoomVar));
+ builder->getUniformCStr(fOffsetVar),
+ coords,
+ builder->getUniformCStr(fZoomVar));
code->appendf("\t\tvec2 delta = min(coord, vec2(1.0, 1.0) - coord);\n");
- code->appendf(
- "\t\tdelta = delta / %s;\n", state->getUniformCStr(fInsetVar));
+ code->appendf("\t\tdelta = delta / %s;\n", builder->getUniformCStr(fInsetVar));
code->appendf("\t\tfloat weight = 0.0;\n");
code->appendf("\t\tif (delta.s < 2.0 && delta.t < 2.0) {\n");
@@ -151,13 +149,12 @@ void GrGLMagnifierEffect::emitFS(GrGLShaderBuilder* state,
code->appendf("\t\t\tweight = min(dist * dist, 1.0);\n");
code->appendf("\t\t} else {\n");
code->appendf("\t\t\tvec2 delta_squared = delta * delta;\n");
- code->appendf(
- "\t\t\tweight = min(min(delta_squared.s, delta_squared.y), 1.0);\n");
+ code->appendf("\t\t\tweight = min(min(delta_squared.s, delta_squared.y), 1.0);\n");
code->appendf("\t\t}\n");
code->appendf("\t\tvec2 mix_coord = mix(coord, zoom_coord, weight);\n");
code->appendf("\t\tvec4 output_color = ");
- state->appendTextureLookup(code, samplers[0], "mix_coord");
+ builder->appendTextureLookup(code, samplers[0], "mix_coord");
code->append(";\n");
code->appendf("\t\t%s = output_color;", outputColor);
@@ -165,29 +162,29 @@ void GrGLMagnifierEffect::emitFS(GrGLShaderBuilder* state,
}
void GrGLMagnifierEffect::setData(const GrGLUniformManager& uman,
- const GrCustomStage& data,
- const GrRenderTarget*,
- int stageNum) {
- const GrMagnifierEffect& zoom =
- static_cast<const GrMagnifierEffect&>(data);
+ const GrEffectStage& stage) {
+ const GrMagnifierEffect& zoom = static_cast<const GrMagnifierEffect&>(*stage.getEffect());
uman.set2f(fOffsetVar, zoom.x_offset(), zoom.y_offset());
uman.set2f(fZoomVar, zoom.x_zoom(), zoom.y_zoom());
uman.set2f(fInsetVar, zoom.x_inset(), zoom.y_inset());
+ fEffectMatrix.setData(uman, zoom.getMatrix(), stage.getCoordChangeMatrix(), zoom.texture(0));
}
-GrGLProgramStage::StageKey GrGLMagnifierEffect::GenKey(const GrCustomStage& s,
- const GrGLCaps& caps) {
- return 0;
+GrGLEffect::EffectKey GrGLMagnifierEffect::GenKey(const GrEffectStage& stage, const GrGLCaps&) {
+ const GrMagnifierEffect& zoom = static_cast<const GrMagnifierEffect&>(*stage.getEffect());
+ return GrGLEffectMatrix::GenKey(zoom.getMatrix(),
+ stage.getCoordChangeMatrix(),
+ zoom.texture(0));
}
/////////////////////////////////////////////////////////////////////
-GR_DEFINE_CUSTOM_STAGE_TEST(GrMagnifierEffect);
+GR_DEFINE_EFFECT_TEST(GrMagnifierEffect);
-GrCustomStage* GrMagnifierEffect::TestCreate(SkRandom* random,
- GrContext* context,
- GrTexture** textures) {
+GrEffect* GrMagnifierEffect::TestCreate(SkRandom* random,
+ GrContext* context,
+ GrTexture** textures) {
const int kMaxWidth = 200;
const int kMaxHeight = 200;
const int kMaxInset = 20;
@@ -202,20 +199,19 @@ GrCustomStage* GrMagnifierEffect::TestCreate(SkRandom* random,
SkRect::MakeXYWH(SkIntToScalar(x), SkIntToScalar(y),
SkIntToScalar(width), SkIntToScalar(height)),
inset));
- GrSamplerState sampler;
- GrCustomStage* stage;
- filter->asNewCustomStage(&stage, textures[0]);
- GrAssert(NULL != stage);
- return stage;
+ GrEffect* effect;
+ filter->asNewEffect(&effect, textures[0]);
+ GrAssert(NULL != effect);
+ return effect;
}
///////////////////////////////////////////////////////////////////////////////
-const GrProgramStageFactory& GrMagnifierEffect::getFactory() const {
- return GrTProgramStageFactory<GrMagnifierEffect>::getInstance();
+const GrBackendEffectFactory& GrMagnifierEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrMagnifierEffect>::getInstance();
}
-bool GrMagnifierEffect::isEqual(const GrCustomStage& sBase) const {
+bool GrMagnifierEffect::isEqual(const GrEffect& sBase) const {
const GrMagnifierEffect& s =
static_cast<const GrMagnifierEffect&>(sBase);
return (this->fXOffset == s.fXOffset &&
@@ -245,18 +241,17 @@ SkMagnifierImageFilter::SkMagnifierImageFilter(SkRect srcRect, SkScalar inset)
SkASSERT(srcRect.x() >= 0 && srcRect.y() >= 0 && inset >= 0);
}
-bool SkMagnifierImageFilter::asNewCustomStage(GrCustomStage** stage,
- GrTexture* texture) const {
+bool SkMagnifierImageFilter::asNewEffect(GrEffect** effect,
+ GrTexture* texture) const {
#if SK_SUPPORT_GPU
- if (stage) {
- *stage =
- SkNEW_ARGS(GrMagnifierEffect, (texture,
- fSrcRect.x() / texture->width(),
- fSrcRect.y() / texture->height(),
- texture->width() / fSrcRect.width(),
- texture->height() / fSrcRect.height(),
- fInset / texture->width(),
- fInset / texture->height()));
+ if (effect) {
+ *effect = SkNEW_ARGS(GrMagnifierEffect, (texture,
+ fSrcRect.x() / texture->width(),
+ fSrcRect.y() / texture->height(),
+ texture->width() / fSrcRect.width(),
+ texture->height() / fSrcRect.height(),
+ fInset / texture->width(),
+ fInset / texture->height()));
}
return true;
#else
diff --git a/src/effects/SkMatrixConvolutionImageFilter.cpp b/src/effects/SkMatrixConvolutionImageFilter.cpp
index 234a74ac05..b5188b324b 100644
--- a/src/effects/SkMatrixConvolutionImageFilter.cpp
+++ b/src/effects/SkMatrixConvolutionImageFilter.cpp
@@ -13,7 +13,13 @@
#include "SkUnPreMultiply.h"
#if SK_SUPPORT_GPU
-#include "gl/GrGLProgramStage.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
+#include "effects/GrSingleTextureEffect.h"
+#include "GrTBackendEffectFactory.h"
+#include "GrTexture.h"
+#include "SkMatrix.h"
+
#endif
SkMatrixConvolutionImageFilter::SkMatrixConvolutionImageFilter(const SkISize& kernelSize, const SkScalar* kernel, SkScalar gain, SkScalar bias, const SkIPoint& target, TileMode tileMode, bool convolveAlpha, SkImageFilter* input)
@@ -41,8 +47,8 @@ SkMatrixConvolutionImageFilter::SkMatrixConvolutionImageFilter(SkFlattenableRead
SkASSERT(readSize == size);
fGain = buffer.readScalar();
fBias = buffer.readScalar();
- fTarget.fX = buffer.readScalar();
- fTarget.fY = buffer.readScalar();
+ fTarget.fX = buffer.readInt();
+ fTarget.fY = buffer.readInt();
fTileMode = (TileMode) buffer.readInt();
fConvolveAlpha = buffer.readBool();
}
@@ -54,8 +60,8 @@ void SkMatrixConvolutionImageFilter::flatten(SkFlattenableWriteBuffer& buffer) c
buffer.writeScalarArray(fKernel, fKernelSize.fWidth * fKernelSize.fHeight);
buffer.writeScalar(fGain);
buffer.writeScalar(fBias);
- buffer.writeScalar(fTarget.fX);
- buffer.writeScalar(fTarget.fY);
+ buffer.writeInt(fTarget.fX);
+ buffer.writeInt(fTarget.fY);
buffer.writeInt((int) fTileMode);
buffer.writeBool(fConvolveAlpha);
}
@@ -260,10 +266,10 @@ public:
TileMode tileMode() const { return fTileMode; }
bool convolveAlpha() const { return fConvolveAlpha; }
- typedef GrGLMatrixConvolutionEffect GLProgramStage;
+ typedef GrGLMatrixConvolutionEffect GLEffect;
- virtual const GrProgramStageFactory& getFactory() const SK_OVERRIDE;
- virtual bool isEqual(const GrCustomStage&) const SK_OVERRIDE;
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+ virtual bool isEqual(const GrEffect&) const SK_OVERRIDE;
private:
SkISize fKernelSize;
@@ -274,71 +280,59 @@ private:
TileMode fTileMode;
bool fConvolveAlpha;
- GR_DECLARE_CUSTOM_STAGE_TEST;
+ GR_DECLARE_EFFECT_TEST;
typedef GrSingleTextureEffect INHERITED;
};
-class GrGLMatrixConvolutionEffect : public GrGLProgramStage {
+class GrGLMatrixConvolutionEffect : public GrGLEffect {
public:
- GrGLMatrixConvolutionEffect(const GrProgramStageFactory& factory,
- const GrCustomStage& stage);
- virtual void setupVariables(GrGLShaderBuilder* builder) SK_OVERRIDE;
- virtual void emitVS(GrGLShaderBuilder* state,
- const char* vertexCoords) SK_OVERRIDE {}
- virtual void emitFS(GrGLShaderBuilder* state,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray&) SK_OVERRIDE;
-
- static inline StageKey GenKey(const GrCustomStage& s, const GrGLCaps& caps);
-
- virtual void setData(const GrGLUniformManager&,
- const GrCustomStage&,
- const GrRenderTarget*,
- int stageNum) SK_OVERRIDE;
+ GrGLMatrixConvolutionEffect(const GrBackendEffectFactory& factory,
+ const GrEffect& effect);
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrEffectStage&,
+ EffectKey,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ static inline EffectKey GenKey(const GrEffectStage&, const GrGLCaps&);
+
+ virtual void setData(const GrGLUniformManager&, const GrEffectStage&) SK_OVERRIDE;
private:
typedef GrGLUniformManager::UniformHandle UniformHandle;
typedef SkMatrixConvolutionImageFilter::TileMode TileMode;
- SkISize fKernelSize;
- TileMode fTileMode;
- bool fConvolveAlpha;
-
- UniformHandle fKernelUni;
- UniformHandle fImageIncrementUni;
- UniformHandle fTargetUni;
- UniformHandle fGainUni;
- UniformHandle fBiasUni;
+ SkISize fKernelSize;
+ TileMode fTileMode;
+ bool fConvolveAlpha;
+
+ UniformHandle fKernelUni;
+ UniformHandle fImageIncrementUni;
+ UniformHandle fTargetUni;
+ UniformHandle fGainUni;
+ UniformHandle fBiasUni;
+
+ GrGLEffectMatrix fEffectMatrix;
+
+ typedef GrGLEffect INHERITED;
};
-GrGLMatrixConvolutionEffect::GrGLMatrixConvolutionEffect(const GrProgramStageFactory& factory,
- const GrCustomStage& stage)
- : GrGLProgramStage(factory)
+GrGLMatrixConvolutionEffect::GrGLMatrixConvolutionEffect(const GrBackendEffectFactory& factory,
+ const GrEffect& effect)
+ : INHERITED(factory)
, fKernelUni(GrGLUniformManager::kInvalidUniformHandle)
, fImageIncrementUni(GrGLUniformManager::kInvalidUniformHandle)
, fTargetUni(GrGLUniformManager::kInvalidUniformHandle)
, fGainUni(GrGLUniformManager::kInvalidUniformHandle)
, fBiasUni(GrGLUniformManager::kInvalidUniformHandle) {
- const GrMatrixConvolutionEffect& m = static_cast<const GrMatrixConvolutionEffect&>(stage);
+ const GrMatrixConvolutionEffect& m = static_cast<const GrMatrixConvolutionEffect&>(effect);
fKernelSize = m.kernelSize();
fTileMode = m.tileMode();
fConvolveAlpha = m.convolveAlpha();
}
-void GrGLMatrixConvolutionEffect::setupVariables(GrGLShaderBuilder* builder) {
- fImageIncrementUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
- kVec2f_GrSLType, "ImageIncrement");
- fKernelUni = builder->addUniformArray(GrGLShaderBuilder::kFragment_ShaderType,
- kFloat_GrSLType, "Kernel", fKernelSize.width() * fKernelSize.height());
- fTargetUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
- kVec2f_GrSLType, "Target");
- fGainUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
- kFloat_GrSLType, "Gain");
- fBiasUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
- kFloat_GrSLType, "Bias");
-}
-
static void appendTextureLookup(GrGLShaderBuilder* builder,
const GrGLShaderBuilder::TextureSampler& sampler,
const char* coord,
@@ -361,10 +355,26 @@ static void appendTextureLookup(GrGLShaderBuilder* builder,
builder->appendTextureLookup(code, sampler, coord);
}
-void GrGLMatrixConvolutionEffect::emitFS(GrGLShaderBuilder* builder,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray& samplers) {
+void GrGLMatrixConvolutionEffect::emitCode(GrGLShaderBuilder* builder,
+ const GrEffectStage&,
+ EffectKey key,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ const char* coords;
+ fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, vertexCoords, &coords);
+ fImageIncrementUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec2f_GrSLType, "ImageIncrement");
+ fKernelUni = builder->addUniformArray(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "Kernel", fKernelSize.width() * fKernelSize.height());
+ fTargetUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec2f_GrSLType, "Target");
+ fGainUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "Gain");
+ fBiasUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "Bias");
+
SkString* code = &builder->fFSCode;
const char* target = builder->getUniformCStr(fTargetUni);
@@ -376,8 +386,7 @@ void GrGLMatrixConvolutionEffect::emitFS(GrGLShaderBuilder* builder,
int kHeight = fKernelSize.height();
code->appendf("\t\tvec4 sum = vec4(0, 0, 0, 0);\n");
- code->appendf("\t\tvec2 coord = %s - %s * %s;\n",
- builder->defaultTexCoordsName(), target, imgInc);
+ code->appendf("\t\tvec2 coord = %s - %s * %s;\n", coords, target, imgInc);
code->appendf("\t\tfor (int y = 0; y < %d; y++) {\n", kHeight);
code->appendf("\t\t\tfor (int x = 0; x < %d; x++) {\n", kWidth);
code->appendf("\t\t\t\tfloat k = %s[y * %d + x];\n", kernel, kWidth);
@@ -396,7 +405,7 @@ void GrGLMatrixConvolutionEffect::emitFS(GrGLShaderBuilder* builder,
code->appendf("\t\t%s.rgb = clamp(%s.rgb, 0.0, %s.a);\n", outputColor, outputColor, outputColor);
} else {
code->appendf("\t\tvec4 c = ");
- appendTextureLookup(builder, samplers[0], builder->defaultTexCoordsName(), fTileMode);
+ appendTextureLookup(builder, samplers[0], coords, fTileMode);
code->appendf(";\n");
code->appendf("\t\t%s.a = c.a;\n", outputColor);
code->appendf("\t\t%s.rgb = sum.rgb * %s + %s;\n", outputColor, gain, bias);
@@ -416,23 +425,24 @@ int encodeXY(int x, int y) {
};
-GrGLProgramStage::StageKey GrGLMatrixConvolutionEffect::GenKey(const GrCustomStage& s,
- const GrGLCaps& caps) {
- const GrMatrixConvolutionEffect& m = static_cast<const GrMatrixConvolutionEffect&>(s);
- StageKey key = encodeXY(m.kernelSize().width(), m.kernelSize().height());
+GrGLEffect::EffectKey GrGLMatrixConvolutionEffect::GenKey(const GrEffectStage& s, const GrGLCaps&) {
+ const GrMatrixConvolutionEffect& m =
+ static_cast<const GrMatrixConvolutionEffect&>(*s.getEffect());
+ EffectKey key = encodeXY(m.kernelSize().width(), m.kernelSize().height());
key |= m.tileMode() << 7;
key |= m.convolveAlpha() ? 1 << 9 : 0;
- return key;
+ key <<= GrGLEffectMatrix::kKeyBits;
+ EffectKey matrixKey = GrGLEffectMatrix::GenKey(m.getMatrix(),
+ s.getCoordChangeMatrix(),
+ m.texture(0));
+ return key | matrixKey;
}
void GrGLMatrixConvolutionEffect::setData(const GrGLUniformManager& uman,
- const GrCustomStage& data,
- const GrRenderTarget*,
- int stageNum) {
+ const GrEffectStage& stage) {
const GrMatrixConvolutionEffect& effect =
- static_cast<const GrMatrixConvolutionEffect&>(data);
- GrGLTexture& texture =
- *static_cast<GrGLTexture*>(data.texture(0));
+ static_cast<const GrMatrixConvolutionEffect&>(*stage.getEffect());
+ GrTexture& texture = *effect.texture(0);
// the code we generated was for a specific kernel size
GrAssert(effect.kernelSize() == fKernelSize);
GrAssert(effect.tileMode() == fTileMode);
@@ -444,6 +454,10 @@ void GrGLMatrixConvolutionEffect::setData(const GrGLUniformManager& uman,
uman.set1fv(fKernelUni, 0, fKernelSize.width() * fKernelSize.height(), effect.kernel());
uman.set1f(fGainUni, effect.gain());
uman.set1f(fBiasUni, effect.bias());
+ fEffectMatrix.setData(uman,
+ effect.getMatrix(),
+ stage.getCoordChangeMatrix(),
+ effect.texture(0));
}
GrMatrixConvolutionEffect::GrMatrixConvolutionEffect(GrTexture* texture,
@@ -454,7 +468,7 @@ GrMatrixConvolutionEffect::GrMatrixConvolutionEffect(GrTexture* texture,
const SkIPoint& target,
TileMode tileMode,
bool convolveAlpha)
- : INHERITED(texture),
+ : INHERITED(texture, MakeDivByTextureWHMatrix(texture)),
fKernelSize(kernelSize),
fGain(SkScalarToFloat(gain)),
fBias(SkScalarToFloat(bias) / 255.0f),
@@ -464,19 +478,19 @@ GrMatrixConvolutionEffect::GrMatrixConvolutionEffect(GrTexture* texture,
for (int i = 0; i < kernelSize.width() * kernelSize.height(); i++) {
fKernel[i] = SkScalarToFloat(kernel[i]);
}
- fTarget[0] = target.x();
- fTarget[1] = target.y();
+ fTarget[0] = static_cast<float>(target.x());
+ fTarget[1] = static_cast<float>(target.y());
}
GrMatrixConvolutionEffect::~GrMatrixConvolutionEffect() {
delete[] fKernel;
}
-const GrProgramStageFactory& GrMatrixConvolutionEffect::getFactory() const {
- return GrTProgramStageFactory<GrMatrixConvolutionEffect>::getInstance();
+const GrBackendEffectFactory& GrMatrixConvolutionEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrMatrixConvolutionEffect>::getInstance();
}
-bool GrMatrixConvolutionEffect::isEqual(const GrCustomStage& sBase) const {
+bool GrMatrixConvolutionEffect::isEqual(const GrEffect& sBase) const {
const GrMatrixConvolutionEffect& s =
static_cast<const GrMatrixConvolutionEffect&>(sBase);
return INHERITED::isEqual(sBase) &&
@@ -489,17 +503,17 @@ bool GrMatrixConvolutionEffect::isEqual(const GrCustomStage& sBase) const {
fConvolveAlpha == s.convolveAlpha();
}
-GR_DEFINE_CUSTOM_STAGE_TEST(GrMatrixConvolutionEffect);
+GR_DEFINE_EFFECT_TEST(GrMatrixConvolutionEffect);
// A little bit less than the minimum # uniforms required by DX9SM2 (32).
// Allows for a 5x5 kernel (or 25x1, for that matter).
#define MAX_KERNEL_SIZE 25
-GrCustomStage* GrMatrixConvolutionEffect::TestCreate(SkRandom* random,
- GrContext* context,
- GrTexture* textures[]) {
- int texIdx = random->nextBool() ? GrCustomStageUnitTest::kSkiaPMTextureIdx :
- GrCustomStageUnitTest::kAlphaTextureIdx;
+GrEffect* GrMatrixConvolutionEffect::TestCreate(SkRandom* random,
+ GrContext* context,
+ GrTexture* textures[]) {
+ int texIdx = random->nextBool() ? GrEffectUnitTest::kSkiaPMTextureIdx :
+ GrEffectUnitTest::kAlphaTextureIdx;
int width = random->nextRangeU(1, MAX_KERNEL_SIZE);
int height = random->nextRangeU(1, MAX_KERNEL_SIZE / width);
SkISize kernelSize = SkISize::Make(width, height);
@@ -524,18 +538,18 @@ GrCustomStage* GrMatrixConvolutionEffect::TestCreate(SkRandom* random,
}
-bool SkMatrixConvolutionImageFilter::asNewCustomStage(GrCustomStage** stage,
- GrTexture* texture) const {
+bool SkMatrixConvolutionImageFilter::asNewEffect(GrEffect** effect,
+ GrTexture* texture) const {
bool ok = fKernelSize.width() * fKernelSize.height() <= MAX_KERNEL_SIZE;
- if (ok && stage) {
- *stage = SkNEW_ARGS(GrMatrixConvolutionEffect, (texture,
- fKernelSize,
- fKernel,
- fGain,
- fBias,
- fTarget,
- fTileMode,
- fConvolveAlpha));
+ if (ok && effect) {
+ *effect = SkNEW_ARGS(GrMatrixConvolutionEffect, (texture,
+ fKernelSize,
+ fKernel,
+ fGain,
+ fBias,
+ fTarget,
+ fTileMode,
+ fConvolveAlpha));
}
return ok;
}
diff --git a/src/effects/SkMergeImageFilter.cpp b/src/effects/SkMergeImageFilter.cpp
new file mode 100755
index 0000000000..0c1388f944
--- /dev/null
+++ b/src/effects/SkMergeImageFilter.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkMergeImageFilter.h"
+#include "SkCanvas.h"
+#include "SkDevice.h"
+#include "SkFlattenableBuffers.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkMergeImageFilter::initAllocModes() {
+ int inputCount = countInputs();
+ if (inputCount) {
+ size_t size = sizeof(uint8_t) * inputCount;
+ if (size <= sizeof(fStorage)) {
+ fModes = SkTCast<uint8_t*>(fStorage);
+ } else {
+ fModes = SkTCast<uint8_t*>(sk_malloc_throw(size));
+ }
+ } else {
+ fModes = NULL;
+ }
+}
+
+void SkMergeImageFilter::initModes(const SkXfermode::Mode modes[]) {
+ if (modes) {
+ this->initAllocModes();
+ int inputCount = countInputs();
+ for (int i = 0; i < inputCount; ++i) {
+ fModes[i] = SkToU8(modes[i]);
+ }
+ } else {
+ fModes = NULL;
+ }
+}
+
+SkMergeImageFilter::SkMergeImageFilter(SkImageFilter* first, SkImageFilter* second,
+ SkXfermode::Mode mode) : INHERITED(first, second) {
+ if (SkXfermode::kSrcOver_Mode != mode) {
+ SkXfermode::Mode modes[] = { mode, mode };
+ this->initModes(modes);
+ } else {
+ fModes = NULL;
+ }
+}
+
+SkMergeImageFilter::SkMergeImageFilter(SkImageFilter* filters[], int count,
+ const SkXfermode::Mode modes[]) : INHERITED(count, filters) {
+ this->initModes(modes);
+}
+
+SkMergeImageFilter::~SkMergeImageFilter() {
+
+ if (fModes != SkTCast<uint8_t*>(fStorage)) {
+ sk_free(fModes);
+ }
+}
+
+bool SkMergeImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
+ SkIRect* dst) {
+ if (countInputs() < 1) {
+ return false;
+ }
+
+ SkIRect totalBounds;
+
+ int inputCount = countInputs();
+ for (int i = 0; i < inputCount; ++i) {
+ SkImageFilter* filter = getInput(i);
+ SkIRect r;
+ if (filter) {
+ if (!filter->filterBounds(src, ctm, &r)) {
+ return false;
+ }
+ } else {
+ r = src;
+ }
+ if (0 == i) {
+ totalBounds = r;
+ } else {
+ totalBounds.join(r);
+ }
+ }
+
+ // don't modify dst until now, so we don't accidentally change it in the
+ // loop, but then return false on the next filter.
+ *dst = totalBounds;
+ return true;
+}
+
+bool SkMergeImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& src,
+ const SkMatrix& ctm,
+ SkBitmap* result, SkIPoint* loc) {
+ if (countInputs() < 1) {
+ return false;
+ }
+
+ const SkIRect srcBounds = SkIRect::MakeXYWH(loc->x(), loc->y(),
+ src.width(), src.height());
+ SkIRect bounds;
+ if (!this->filterBounds(srcBounds, ctm, &bounds)) {
+ return false;
+ }
+
+ const int x0 = bounds.left();
+ const int y0 = bounds.top();
+
+ SkAutoTUnref<SkDevice> dst(proxy->createDevice(bounds.width(), bounds.height()));
+ if (NULL == dst) {
+ return false;
+ }
+ SkCanvas canvas(dst);
+ SkPaint paint;
+
+ int inputCount = countInputs();
+ for (int i = 0; i < inputCount; ++i) {
+ SkBitmap tmp;
+ const SkBitmap* srcPtr;
+ SkIPoint pos = *loc;
+ SkImageFilter* filter = getInput(i);
+ if (filter) {
+ if (!filter->filterImage(proxy, src, ctm, &tmp, &pos)) {
+ return false;
+ }
+ srcPtr = &tmp;
+ } else {
+ srcPtr = &src;
+ }
+
+ if (fModes) {
+ paint.setXfermodeMode((SkXfermode::Mode)fModes[i]);
+ } else {
+ paint.setXfermode(NULL);
+ }
+ canvas.drawSprite(*srcPtr, pos.x() - x0, pos.y() - y0, &paint);
+ }
+
+ loc->set(bounds.left(), bounds.top());
+ *result = dst->accessBitmap(false);
+ return true;
+}
+
+void SkMergeImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+
+ buffer.writeBool(fModes != NULL);
+ if (fModes) {
+ buffer.writeByteArray(fModes, countInputs() * sizeof(fModes[0]));
+ }
+}
+
+SkMergeImageFilter::SkMergeImageFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ bool hasModes = buffer.readBool();
+ if (hasModes) {
+ this->initAllocModes();
+ SkASSERT(buffer.getArrayCount() == countInputs() * sizeof(fModes[0]));
+ buffer.readByteArray(fModes);
+ } else {
+ fModes = 0;
+ }
+}
diff --git a/src/effects/SkMorphologyImageFilter.cpp b/src/effects/SkMorphologyImageFilter.cpp
index 9bddb9b9e6..a497346b88 100644
--- a/src/effects/SkMorphologyImageFilter.cpp
+++ b/src/effects/SkMorphologyImageFilter.cpp
@@ -13,8 +13,9 @@
#if SK_SUPPORT_GPU
#include "GrContext.h"
#include "GrTexture.h"
-#include "GrGpu.h"
-#include "gl/GrGLProgramStage.h"
+#include "GrTBackendEffectFactory.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
#include "effects/Gr1DKernelEffect.h"
#endif
@@ -249,42 +250,39 @@ public:
static const char* Name() { return "Morphology"; }
- typedef GrGLMorphologyEffect GLProgramStage;
+ typedef GrGLMorphologyEffect GLEffect;
- virtual const GrProgramStageFactory& getFactory() const SK_OVERRIDE;
- virtual bool isEqual(const GrCustomStage&) const SK_OVERRIDE;
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+ virtual bool isEqual(const GrEffect&) const SK_OVERRIDE;
protected:
MorphologyType fType;
private:
- GR_DECLARE_CUSTOM_STAGE_TEST;
+ GR_DECLARE_EFFECT_TEST;
typedef Gr1DKernelEffect INHERITED;
};
///////////////////////////////////////////////////////////////////////////////
-class GrGLMorphologyEffect : public GrGLProgramStage {
+class GrGLMorphologyEffect : public GrGLEffect {
public:
- GrGLMorphologyEffect (const GrProgramStageFactory& factory,
- const GrCustomStage& stage);
+ GrGLMorphologyEffect (const GrBackendEffectFactory& factory,
+ const GrEffect& effect);
- virtual void setupVariables(GrGLShaderBuilder* builder) SK_OVERRIDE;
- virtual void emitVS(GrGLShaderBuilder* state,
- const char* vertexCoords) SK_OVERRIDE {};
- virtual void emitFS(GrGLShaderBuilder* state,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray&) SK_OVERRIDE;
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrEffectStage&,
+ EffectKey,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
- static inline StageKey GenKey(const GrCustomStage& s, const GrGLCaps& caps);
+ static inline EffectKey GenKey(const GrEffectStage&, const GrGLCaps&);
- virtual void setData(const GrGLUniformManager&,
- const GrCustomStage&,
- const GrRenderTarget*,
- int stageNum) SK_OVERRIDE;
+ virtual void setData(const GrGLUniformManager&, const GrEffectStage&) SK_OVERRIDE;
private:
int width() const { return GrMorphologyEffect::WidthFromRadius(fRadius); }
@@ -292,28 +290,32 @@ private:
int fRadius;
GrMorphologyEffect::MorphologyType fType;
GrGLUniformManager::UniformHandle fImageIncrementUni;
+ GrGLEffectMatrix fEffectMatrix;
- typedef GrGLProgramStage INHERITED;
+ typedef GrGLEffect INHERITED;
};
-GrGLMorphologyEffect::GrGLMorphologyEffect(const GrProgramStageFactory& factory,
- const GrCustomStage& stage)
- : GrGLProgramStage(factory)
+GrGLMorphologyEffect::GrGLMorphologyEffect(const GrBackendEffectFactory& factory,
+ const GrEffect& effect)
+ : INHERITED(factory)
, fImageIncrementUni(GrGLUniformManager::kInvalidUniformHandle) {
- const GrMorphologyEffect& m = static_cast<const GrMorphologyEffect&>(stage);
+ const GrMorphologyEffect& m = static_cast<const GrMorphologyEffect&>(effect);
fRadius = m.radius();
fType = m.type();
}
-void GrGLMorphologyEffect::setupVariables(GrGLShaderBuilder* builder) {
+void GrGLMorphologyEffect::emitCode(GrGLShaderBuilder* builder,
+ const GrEffectStage&,
+ EffectKey key,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ const char* coords;
+ fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, vertexCoords, &coords);
fImageIncrementUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
kVec2f_GrSLType, "ImageIncrement");
-}
-void GrGLMorphologyEffect::emitFS(GrGLShaderBuilder* builder,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray& samplers) {
SkString* code = &builder->fFSCode;
const char* func;
@@ -333,8 +335,7 @@ void GrGLMorphologyEffect::emitFS(GrGLShaderBuilder* builder,
}
const char* imgInc = builder->getUniformCStr(fImageIncrementUni);
- code->appendf("\t\tvec2 coord = %s - %d.0 * %s;\n",
- builder->defaultTexCoordsName(), fRadius, imgInc);
+ code->appendf("\t\tvec2 coord = %s - %d.0 * %s;\n", coords, fRadius, imgInc);
code->appendf("\t\tfor (int i = 0; i < %d; i++) {\n", this->width());
code->appendf("\t\t\t%s = %s(%s, ", outputColor, func, outputColor);
builder->appendTextureLookup(&builder->fFSCode, samplers[0], "coord");
@@ -344,22 +345,20 @@ void GrGLMorphologyEffect::emitFS(GrGLShaderBuilder* builder,
GrGLSLMulVarBy4f(code, 2, outputColor, inputColor);
}
-GrGLProgramStage::StageKey GrGLMorphologyEffect::GenKey(const GrCustomStage& s,
- const GrGLCaps& caps) {
- const GrMorphologyEffect& m = static_cast<const GrMorphologyEffect&>(s);
- StageKey key = static_cast<StageKey>(m.radius());
+GrGLEffect::EffectKey GrGLMorphologyEffect::GenKey(const GrEffectStage& s, const GrGLCaps&) {
+ const GrMorphologyEffect& m = static_cast<const GrMorphologyEffect&>(*s.getEffect());
+ EffectKey key = static_cast<EffectKey>(m.radius());
key |= (m.type() << 8);
- return key;
+ key <<= GrGLEffectMatrix::kKeyBits;
+ EffectKey matrixKey = GrGLEffectMatrix::GenKey(m.getMatrix(),
+ s.getCoordChangeMatrix(),
+ m.texture(0));
+ return key | matrixKey;
}
-void GrGLMorphologyEffect::setData(const GrGLUniformManager& uman,
- const GrCustomStage& data,
- const GrRenderTarget*,
- int stageNum) {
- const Gr1DKernelEffect& kern =
- static_cast<const Gr1DKernelEffect&>(data);
- GrGLTexture& texture =
- *static_cast<GrGLTexture*>(data.texture(0));
+void GrGLMorphologyEffect::setData(const GrGLUniformManager& uman, const GrEffectStage& stage) {
+ const Gr1DKernelEffect& kern = static_cast<const Gr1DKernelEffect&>(*stage.getEffect());
+ GrTexture& texture = *kern.texture(0);
// the code we generated was for a specific kernel radius
GrAssert(kern.radius() == fRadius);
float imageIncrement[2] = { 0 };
@@ -374,6 +373,7 @@ void GrGLMorphologyEffect::setData(const GrGLUniformManager& uman,
GrCrash("Unknown filter direction.");
}
uman.set2fv(fImageIncrementUni, 0, 1, imageIncrement);
+ fEffectMatrix.setData(uman, kern.getMatrix(), stage.getCoordChangeMatrix(), kern.texture(0));
}
///////////////////////////////////////////////////////////////////////////////
@@ -389,11 +389,11 @@ GrMorphologyEffect::GrMorphologyEffect(GrTexture* texture,
GrMorphologyEffect::~GrMorphologyEffect() {
}
-const GrProgramStageFactory& GrMorphologyEffect::getFactory() const {
- return GrTProgramStageFactory<GrMorphologyEffect>::getInstance();
+const GrBackendEffectFactory& GrMorphologyEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrMorphologyEffect>::getInstance();
}
-bool GrMorphologyEffect::isEqual(const GrCustomStage& sBase) const {
+bool GrMorphologyEffect::isEqual(const GrEffect& sBase) const {
const GrMorphologyEffect& s =
static_cast<const GrMorphologyEffect&>(sBase);
return (INHERITED::isEqual(sBase) &&
@@ -404,13 +404,13 @@ bool GrMorphologyEffect::isEqual(const GrCustomStage& sBase) const {
///////////////////////////////////////////////////////////////////////////////
-GR_DEFINE_CUSTOM_STAGE_TEST(GrMorphologyEffect);
+GR_DEFINE_EFFECT_TEST(GrMorphologyEffect);
-GrCustomStage* GrMorphologyEffect::TestCreate(SkRandom* random,
- GrContext* context,
- GrTexture* textures[]) {
- int texIdx = random->nextBool() ? GrCustomStageUnitTest::kSkiaPMTextureIdx :
- GrCustomStageUnitTest::kAlphaTextureIdx;
+GrEffect* GrMorphologyEffect::TestCreate(SkRandom* random,
+ GrContext* context,
+ GrTexture* textures[]) {
+ int texIdx = random->nextBool() ? GrEffectUnitTest::kSkiaPMTextureIdx :
+ GrEffectUnitTest::kAlphaTextureIdx;
Direction dir = random->nextBool() ? kX_Direction : kY_Direction;
static const int kMaxRadius = 10;
int radius = random->nextRangeU(1, kMaxRadius);
@@ -428,10 +428,11 @@ void apply_morphology_pass(GrContext* context,
int radius,
GrMorphologyEffect::MorphologyType morphType,
Gr1DKernelEffect::Direction direction) {
- GrMatrix sampleM;
- sampleM.setIDiv(texture->width(), texture->height());
GrPaint paint;
- paint.colorSampler(0)->setCustomStage(SkNEW_ARGS(GrMorphologyEffect, (texture, direction, radius, morphType)), sampleM)->unref();
+ paint.colorStage(0)->setEffect(SkNEW_ARGS(GrMorphologyEffect, (texture,
+ direction,
+ radius,
+ morphType)))->unref();
context->drawRect(paint, rect);
}
diff --git a/src/effects/SkOffsetImageFilter.cpp b/src/effects/SkOffsetImageFilter.cpp
new file mode 100644
index 0000000000..fb39aa8e41
--- /dev/null
+++ b/src/effects/SkOffsetImageFilter.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkOffsetImageFilter.h"
+#include "SkBitmap.h"
+#include "SkMatrix.h"
+#include "SkFlattenableBuffers.h"
+
+bool SkOffsetImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& source,
+ const SkMatrix& matrix,
+ SkBitmap* result,
+ SkIPoint* loc) {
+ SkBitmap src = this->getInputResult(proxy, source, matrix, loc);
+ SkVector vec;
+ matrix.mapVectors(&vec, &fOffset, 1);
+
+ loc->fX += SkScalarRoundToInt(vec.fX);
+ loc->fY += SkScalarRoundToInt(vec.fY);
+ *result = src;
+ return true;
+}
+
+bool SkOffsetImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
+ SkIRect* dst) {
+ SkVector vec;
+ ctm.mapVectors(&vec, &fOffset, 1);
+
+ *dst = src;
+ dst->offset(SkScalarRoundToInt(vec.fX), SkScalarRoundToInt(vec.fY));
+ return true;
+}
+
+void SkOffsetImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writePoint(fOffset);
+}
+
+SkOffsetImageFilter::SkOffsetImageFilter(SkScalar dx, SkScalar dy,
+ SkImageFilter* input) : INHERITED(input) {
+ fOffset.set(dx, dy);
+}
+
+SkOffsetImageFilter::SkOffsetImageFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ buffer.readPoint(&fOffset);
+}
diff --git a/src/effects/SkPaintFlagsDrawFilter.cpp b/src/effects/SkPaintFlagsDrawFilter.cpp
index 1fe440208c..92d6f642f4 100644
--- a/src/effects/SkPaintFlagsDrawFilter.cpp
+++ b/src/effects/SkPaintFlagsDrawFilter.cpp
@@ -1,4 +1,3 @@
-
/*
* Copyright 2011 Google Inc.
*
@@ -6,7 +5,6 @@
* found in the LICENSE file.
*/
-
#include "SkPaintFlagsDrawFilter.h"
#include "SkPaint.h"
@@ -16,7 +14,8 @@ SkPaintFlagsDrawFilter::SkPaintFlagsDrawFilter(uint32_t clearFlags,
fSetFlags = SkToU16(setFlags & SkPaint::kAllFlags);
}
-void SkPaintFlagsDrawFilter::filter(SkPaint* paint, Type) {
+bool SkPaintFlagsDrawFilter::filter(SkPaint* paint, Type) {
paint->setFlags((paint->getFlags() & ~fClearFlags) | fSetFlags);
+ return true;
}
diff --git a/src/effects/SkSingleInputImageFilter.cpp b/src/effects/SkSingleInputImageFilter.cpp
index 2019e251e8..76150386f4 100644
--- a/src/effects/SkSingleInputImageFilter.cpp
+++ b/src/effects/SkSingleInputImageFilter.cpp
@@ -15,7 +15,7 @@
#include "SkGrPixelRef.h"
#endif
-SkSingleInputImageFilter::SkSingleInputImageFilter(SkImageFilter* input) : INHERITED(1, input) {
+SkSingleInputImageFilter::SkSingleInputImageFilter(SkImageFilter* input) : INHERITED(input) {
}
SkSingleInputImageFilter::~SkSingleInputImageFilter() {
diff --git a/src/effects/SkStippleMaskFilter.cpp b/src/effects/SkStippleMaskFilter.cpp
index 434528ed91..24b9114400 100644
--- a/src/effects/SkStippleMaskFilter.cpp
+++ b/src/effects/SkStippleMaskFilter.cpp
@@ -11,7 +11,7 @@
bool SkStippleMaskFilter::filterMask(SkMask* dst,
const SkMask& src,
const SkMatrix& matrix,
- SkIPoint* margin) {
+ SkIPoint* margin) const {
if (src.fFormat != SkMask::kA8_Format) {
return false;
diff --git a/src/effects/SkTableColorFilter.cpp b/src/effects/SkTableColorFilter.cpp
index fcfb444a3f..ad34fbb6c6 100644
--- a/src/effects/SkTableColorFilter.cpp
+++ b/src/effects/SkTableColorFilter.cpp
@@ -38,10 +38,14 @@ public:
SkDELETE(fBitmap);
}
- virtual bool asComponentTable(SkBitmap* table) SK_OVERRIDE;
+ virtual bool asComponentTable(SkBitmap* table) const SK_OVERRIDE;
+
+#if SK_SUPPORT_GPU
+ virtual GrEffect* asNewEffect(GrContext* context) const SK_OVERRIDE;
+#endif
virtual void filterSpan(const SkPMColor src[], int count,
- SkPMColor dst[]) SK_OVERRIDE;
+ SkPMColor dst[]) const SK_OVERRIDE;
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkTable_ColorFilter)
@@ -50,7 +54,7 @@ protected:
virtual void flatten(SkFlattenableWriteBuffer&) const SK_OVERRIDE;
private:
- SkBitmap* fBitmap;
+ mutable const SkBitmap* fBitmap; // lazily allocated
enum {
kA_Flag = 1 << 0,
@@ -100,7 +104,7 @@ static const uint8_t gIdentityTable[] = {
};
void SkTable_ColorFilter::filterSpan(const SkPMColor src[], int count,
- SkPMColor dst[]) {
+ SkPMColor dst[]) const {
const uint8_t* table = fStorage;
const uint8_t* tableA = gIdentityTable;
const uint8_t* tableR = gIdentityTable;
@@ -184,13 +188,13 @@ SkTable_ColorFilter::SkTable_ColorFilter(SkFlattenableReadBuffer& buffer) : INHE
SkASSERT(raw == count * 256);
}
-bool SkTable_ColorFilter::asComponentTable(SkBitmap* table) {
+bool SkTable_ColorFilter::asComponentTable(SkBitmap* table) const {
if (table) {
if (NULL == fBitmap) {
- fBitmap = SkNEW(SkBitmap);
- fBitmap->setConfig(SkBitmap::kA8_Config, 256, 4, 256);
- fBitmap->allocPixels();
- uint8_t* bitmapPixels = fBitmap->getAddr8(0, 0);
+ SkBitmap* bmp = SkNEW(SkBitmap);
+ bmp->setConfig(SkBitmap::kA8_Config, 256, 4, 256);
+ bmp->allocPixels();
+ uint8_t* bitmapPixels = bmp->getAddr8(0, 0);
int offset = 0;
static const unsigned kFlags[] = { kA_Flag, kR_Flag, kG_Flag, kB_Flag };
@@ -203,12 +207,169 @@ bool SkTable_ColorFilter::asComponentTable(SkBitmap* table) {
}
bitmapPixels += 256;
}
+ fBitmap = bmp;
}
*table = *fBitmap;
}
return true;
}
+#if SK_SUPPORT_GPU
+
+#include "GrEffect.h"
+#include "GrTBackendEffectFactory.h"
+#include "gl/GrGLEffect.h"
+#include "SkGr.h"
+
+class GLColorTableEffect;
+
+class ColorTableEffect : public GrEffect {
+public:
+
+ explicit ColorTableEffect(GrTexture* texture);
+ virtual ~ColorTableEffect();
+
+ static const char* Name() { return "ColorTable"; }
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+ virtual bool isEqual(const GrEffect&) const SK_OVERRIDE;
+
+ virtual const GrTextureAccess& textureAccess(int index) const SK_OVERRIDE;
+
+ typedef GLColorTableEffect GLEffect;
+
+private:
+ GR_DECLARE_EFFECT_TEST;
+
+ GrTextureAccess fTextureAccess;
+
+ typedef GrEffect INHERITED;
+};
+
+class GLColorTableEffect : public GrGLEffect {
+public:
+ GLColorTableEffect(const GrBackendEffectFactory& factory,
+ const GrEffect& effect);
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrEffectStage&,
+ EffectKey,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ virtual void setData(const GrGLUniformManager&, const GrEffectStage&) SK_OVERRIDE {}
+
+ static EffectKey GenKey(const GrEffectStage&, const GrGLCaps&);
+
+private:
+
+ typedef GrGLEffect INHERITED;
+};
+
+GLColorTableEffect::GLColorTableEffect(
+ const GrBackendEffectFactory& factory, const GrEffect& effect)
+ : INHERITED(factory) {
+ }
+
+void GLColorTableEffect::emitCode(GrGLShaderBuilder* builder,
+ const GrEffectStage&,
+ EffectKey,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+
+ static const float kColorScaleFactor = 255.0f / 256.0f;
+ static const float kColorOffsetFactor = 1.0f / 512.0f;
+ SkString* code = &builder->fFSCode;
+ if (NULL == inputColor) {
+ // the input color is solid white (all ones).
+ static const float kMaxValue = kColorScaleFactor + kColorOffsetFactor;
+ code->appendf("\t\tvec4 coord = vec4(%f, %f, %f, %f);\n",
+ kMaxValue, kMaxValue, kMaxValue, kMaxValue);
+
+ } else {
+ code->appendf("\t\tfloat nonZeroAlpha = max(%s.a, .0001);\n", inputColor);
+ code->appendf("\t\tvec4 coord = vec4(%s.rgb / nonZeroAlpha, nonZeroAlpha);\n", inputColor);
+ code->appendf("\t\tcoord = coord * %f + vec4(%f, %f, %f, %f);\n",
+ kColorScaleFactor,
+ kColorOffsetFactor, kColorOffsetFactor,
+ kColorOffsetFactor, kColorOffsetFactor);
+ }
+
+ code->appendf("\t\t%s.a = ", outputColor);
+ builder->appendTextureLookup(code, samplers[0], "vec2(coord.a, 0.125)");
+ code->append(";\n");
+
+ code->appendf("\t\t%s.r = ", outputColor);
+ builder->appendTextureLookup(code, samplers[0], "vec2(coord.r, 0.375)");
+ code->append(";\n");
+
+ code->appendf("\t\t%s.g = ", outputColor);
+ builder->appendTextureLookup(code, samplers[0], "vec2(coord.g, 0.625)");
+ code->append(";\n");
+
+ code->appendf("\t\t%s.b = ", outputColor);
+ builder->appendTextureLookup(code, samplers[0], "vec2(coord.b, 0.875)");
+ code->append(";\n");
+
+ code->appendf("\t\t%s.rgb *= %s.a;\n", outputColor, outputColor);
+}
+
+GrGLEffect::EffectKey GLColorTableEffect::GenKey(const GrEffectStage&, const GrGLCaps&) {
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ColorTableEffect::ColorTableEffect(GrTexture* texture)
+ : INHERITED(1)
+ , fTextureAccess(texture, "a") {
+}
+
+ColorTableEffect::~ColorTableEffect() {
+}
+
+const GrBackendEffectFactory& ColorTableEffect::getFactory() const {
+ return GrTBackendEffectFactory<ColorTableEffect>::getInstance();
+}
+
+bool ColorTableEffect::isEqual(const GrEffect& sBase) const {
+ return INHERITED::isEqual(sBase);
+}
+
+const GrTextureAccess& ColorTableEffect::textureAccess(int index) const {
+ GrAssert(0 == index);
+ return fTextureAccess;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_EFFECT_TEST(ColorTableEffect);
+
+GrEffect* ColorTableEffect::TestCreate(SkRandom* random,
+ GrContext* context,
+ GrTexture* textures[]) {
+ return SkNEW_ARGS(ColorTableEffect, (textures[GrEffectUnitTest::kAlphaTextureIdx]));
+}
+
+GrEffect* SkTable_ColorFilter::asNewEffect(GrContext* context) const {
+ SkBitmap bitmap;
+ this->asComponentTable(&bitmap);
+ // passing NULL because this effect does no tiling or filtering.
+ GrTexture* texture = GrLockCachedBitmapTexture(context, bitmap, NULL);
+ GrEffect* effect = SkNEW_ARGS(ColorTableEffect, (texture));
+
+ // Unlock immediately, this is not great, but we don't have a way of
+ // knowing when else to unlock it currently. TODO: Remove this when
+ // unref becomes the unlock replacement for all types of textures.
+ GrUnlockCachedBitmapTexture(texture);
+ return effect;
+}
+
+#endif // SK_SUPPORT_GPU
+
///////////////////////////////////////////////////////////////////////////////
#ifdef SK_CPU_BENDIAN
diff --git a/src/effects/SkTableMaskFilter.cpp b/src/effects/SkTableMaskFilter.cpp
index d1fe134586..73b7fa614c 100644
--- a/src/effects/SkTableMaskFilter.cpp
+++ b/src/effects/SkTableMaskFilter.cpp
@@ -17,17 +17,13 @@ SkTableMaskFilter::SkTableMaskFilter() {
}
SkTableMaskFilter::SkTableMaskFilter(const uint8_t table[256]) {
- this->setTable(table);
+ memcpy(fTable, table, sizeof(fTable));
}
SkTableMaskFilter::~SkTableMaskFilter() {}
-void SkTableMaskFilter::setTable(const uint8_t table[256]) {
- memcpy(fTable, table, 256);
-}
-
bool SkTableMaskFilter::filterMask(SkMask* dst, const SkMask& src,
- const SkMatrix&, SkIPoint* margin) {
+ const SkMatrix&, SkIPoint* margin) const {
if (src.fFormat != SkMask::kA8_Format) {
return false;
}
@@ -68,7 +64,7 @@ bool SkTableMaskFilter::filterMask(SkMask* dst, const SkMask& src,
return true;
}
-SkMask::Format SkTableMaskFilter::getFormat() {
+SkMask::Format SkTableMaskFilter::getFormat() const {
return SkMask::kA8_Format;
}
diff --git a/src/effects/SkTestImageFilters.cpp b/src/effects/SkTestImageFilters.cpp
index a672c337f7..c280555226 100755
--- a/src/effects/SkTestImageFilters.cpp
+++ b/src/effects/SkTestImageFilters.cpp
@@ -18,38 +18,6 @@ public:
}
};
-bool SkOffsetImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& src,
- const SkMatrix& matrix,
- SkBitmap* result,
- SkIPoint* loc) {
- SkVector vec;
- matrix.mapVectors(&vec, &fOffset, 1);
-
- loc->fX += SkScalarRoundToInt(vec.fX);
- loc->fY += SkScalarRoundToInt(vec.fY);
- *result = src;
- return true;
-}
-
-bool SkOffsetImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
- SkIRect* dst) {
- SkVector vec;
- ctm.mapVectors(&vec, &fOffset, 1);
-
- *dst = src;
- dst->offset(SkScalarRoundToInt(vec.fX), SkScalarRoundToInt(vec.fY));
- return true;
-}
-
-void SkOffsetImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
- this->INHERITED::flatten(buffer);
- buffer.writePoint(fOffset);
-}
-
-SkOffsetImageFilter::SkOffsetImageFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
- buffer.readPoint(&fOffset);
-}
-
///////////////////////////////////////////////////////////////////////////////
SkComposeImageFilter::~SkComposeImageFilter() {
@@ -100,160 +68,6 @@ SkComposeImageFilter::SkComposeImageFilter(SkFlattenableReadBuffer& buffer) : IN
///////////////////////////////////////////////////////////////////////////////
-void SkMergeImageFilter::initAllocModes() {
- int inputCount = countInputs();
- if (inputCount) {
- size_t size = sizeof(uint8_t) * inputCount;
- if (size <= sizeof(fStorage)) {
- fModes = SkTCast<uint8_t*>(fStorage);
- } else {
- fModes = SkTCast<uint8_t*>(sk_malloc_throw(size));
- }
- } else {
- fModes = NULL;
- }
-}
-
-void SkMergeImageFilter::initModes(const SkXfermode::Mode modes[]) {
- if (modes) {
- this->initAllocModes();
- int inputCount = countInputs();
- for (int i = 0; i < inputCount; ++i) {
- fModes[i] = SkToU8(modes[i]);
- }
- } else {
- fModes = NULL;
- }
-}
-
-SkMergeImageFilter::SkMergeImageFilter(SkImageFilter* first, SkImageFilter* second,
- SkXfermode::Mode mode) : INHERITED(2, first, second) {
- if (SkXfermode::kSrcOver_Mode != mode) {
- SkXfermode::Mode modes[] = { mode, mode };
- this->initModes(modes);
- } else {
- fModes = NULL;
- }
-}
-
-SkMergeImageFilter::SkMergeImageFilter(SkImageFilter* const filters[], int count,
- const SkXfermode::Mode modes[]) : INHERITED(count, filters) {
- this->initModes(modes);
-}
-
-SkMergeImageFilter::~SkMergeImageFilter() {
-
- if (fModes != SkTCast<uint8_t*>(fStorage)) {
- sk_free(fModes);
- }
-}
-
-bool SkMergeImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
- SkIRect* dst) {
- if (countInputs() < 1) {
- return false;
- }
-
- SkIRect totalBounds;
-
- int inputCount = countInputs();
- for (int i = 0; i < inputCount; ++i) {
- SkImageFilter* filter = getInput(i);
- SkIRect r;
- if (filter) {
- if (!filter->filterBounds(src, ctm, &r)) {
- return false;
- }
- } else {
- r = src;
- }
- if (0 == i) {
- totalBounds = r;
- } else {
- totalBounds.join(r);
- }
- }
-
- // don't modify dst until now, so we don't accidentally change it in the
- // loop, but then return false on the next filter.
- *dst = totalBounds;
- return true;
-}
-
-bool SkMergeImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& src,
- const SkMatrix& ctm,
- SkBitmap* result, SkIPoint* loc) {
- if (countInputs() < 1) {
- return false;
- }
-
- const SkIRect srcBounds = SkIRect::MakeXYWH(loc->x(), loc->y(),
- src.width(), src.height());
- SkIRect bounds;
- if (!this->filterBounds(srcBounds, ctm, &bounds)) {
- return false;
- }
-
- const int x0 = bounds.left();
- const int y0 = bounds.top();
-
- SkDevice* dst = proxy->createDevice(bounds.width(), bounds.height());
- if (NULL == dst) {
- return false;
- }
- OwnDeviceCanvas canvas(dst);
- SkPaint paint;
-
- int inputCount = countInputs();
- for (int i = 0; i < inputCount; ++i) {
- SkBitmap tmp;
- const SkBitmap* srcPtr;
- SkIPoint pos = *loc;
- SkImageFilter* filter = getInput(i);
- if (filter) {
- if (!filter->filterImage(proxy, src, ctm, &tmp, &pos)) {
- return false;
- }
- srcPtr = &tmp;
- } else {
- srcPtr = &src;
- }
-
- if (fModes) {
- paint.setXfermodeMode((SkXfermode::Mode)fModes[i]);
- } else {
- paint.setXfermode(NULL);
- }
- canvas.drawSprite(*srcPtr, pos.x() - x0, pos.y() - y0, &paint);
- }
-
- loc->set(bounds.left(), bounds.top());
- *result = dst->accessBitmap(false);
- return true;
-}
-
-void SkMergeImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
- this->INHERITED::flatten(buffer);
-
- buffer.writeBool(fModes != NULL);
- if (fModes) {
- buffer.writeByteArray(fModes, countInputs() * sizeof(fModes[0]));
- }
-}
-
-SkMergeImageFilter::SkMergeImageFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
- bool hasModes = buffer.readBool();
- if (hasModes) {
- this->initAllocModes();
- SkASSERT(buffer.getArrayCount() == countInputs() * sizeof(fModes[0]));
- buffer.readByteArray(fModes);
- } else {
- fModes = 0;
- }
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
bool SkDownSampleImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& src,
const SkMatrix& matrix,
SkBitmap* result, SkIPoint*) {
diff --git a/src/effects/gradients/SkGradientShader.cpp b/src/effects/gradients/SkGradientShader.cpp
index cc7e13ccb6..5b2a60e944 100644
--- a/src/effects/gradients/SkGradientShader.cpp
+++ b/src/effects/gradients/SkGradientShader.cpp
@@ -213,6 +213,8 @@ bool SkGradientShaderBase::setContext(const SkBitmap& device,
const SkMatrix& inverse = this->getTotalInverse();
if (!fDstToIndex.setConcat(fPtsToUnit, inverse)) {
+ // need to keep our set/end context calls balanced.
+ this->INHERITED::endContext();
return false;
}
@@ -675,40 +677,61 @@ SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
#include "effects/GrTextureStripAtlas.h"
#include "SkGr.h"
-GrGLGradientStage::GrGLGradientStage(const GrProgramStageFactory& factory)
+GrGLGradientEffect::GrGLGradientEffect(const GrBackendEffectFactory& factory)
: INHERITED(factory)
- , fCachedYCoord(GR_ScalarMax)
- , fFSYUni(GrGLUniformManager::kInvalidUniformHandle) { }
+ , fCachedYCoord(SK_ScalarMax)
+ , fFSYUni(GrGLUniformManager::kInvalidUniformHandle) {
+}
-GrGLGradientStage::~GrGLGradientStage() { }
+GrGLGradientEffect::~GrGLGradientEffect() { }
-void GrGLGradientStage::setupVariables(GrGLShaderBuilder* builder) {
+void GrGLGradientEffect::emitYCoordUniform(GrGLShaderBuilder* builder) {
fFSYUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
kFloat_GrSLType, "GradientYCoordFS");
}
-void GrGLGradientStage::setData(const GrGLUniformManager& uman,
- const GrCustomStage& stage,
- const GrRenderTarget*,
- int stageNum) {
- GrScalar yCoord = static_cast<const GrGradientEffect&>(stage).getYCoord();
+void GrGLGradientEffect::setData(const GrGLUniformManager& uman, const GrEffectStage& stage) {
+ const GrGradientEffect& e = static_cast<const GrGradientEffect&>(*stage.getEffect());
+ const GrTexture* texture = e.texture(0);
+ fEffectMatrix.setData(uman, e.getMatrix(), stage.getCoordChangeMatrix(), texture);
+
+ SkScalar yCoord = e.getYCoord();
if (yCoord != fCachedYCoord) {
uman.set1f(fFSYUni, yCoord);
fCachedYCoord = yCoord;
}
}
-void GrGLGradientStage::emitColorLookup(GrGLShaderBuilder* builder,
- const char* gradientTValue,
- const char* outputColor,
- const char* inputColor,
- const GrGLShaderBuilder::TextureSampler& sampler) {
+GrGLEffect::EffectKey GrGLGradientEffect::GenMatrixKey(const GrEffectStage& s) {
+ const GrGradientEffect& e = static_cast<const GrGradientEffect&>(*s.getEffect());
+ const GrTexture* texture = e.texture(0);
+ return GrGLEffectMatrix::GenKey(e.getMatrix(), s.getCoordChangeMatrix(), texture);
+}
+
+void GrGLGradientEffect::setupMatrix(GrGLShaderBuilder* builder,
+ EffectKey key,
+ const char* vertexCoords,
+ const char** fsCoordName,
+ const char** vsVaryingName,
+ GrSLType* vsVaryingType) {
+ fEffectMatrix.emitCodeMakeFSCoords2D(builder,
+ key & kMatrixKeyMask,
+ vertexCoords,
+ fsCoordName,
+ vsVaryingName,
+ vsVaryingType);
+}
+
+void GrGLGradientEffect::emitColorLookup(GrGLShaderBuilder* builder,
+ const char* gradientTValue,
+ const char* outputColor,
+ const char* inputColor,
+ const GrGLShaderBuilder::TextureSampler& sampler) {
SkString* code = &builder->fFSCode;
code->appendf("\tvec2 coord = vec2(%s, %s);\n",
gradientTValue,
builder->getUniformVariable(fFSYUni).c_str());
- GrGLSLMulVarBy4f(code, 1, outputColor, inputColor);
code->appendf("\t%s = ", outputColor);
builder->appendTextureLookupAndModulate(code, inputColor, sampler, "coord");
code->append(";\n");
@@ -718,6 +741,7 @@ void GrGLGradientStage::emitColorLookup(GrGLShaderBuilder* builder,
GrGradientEffect::GrGradientEffect(GrContext* ctx,
const SkGradientShaderBase& shader,
+ const SkMatrix& matrix,
SkShader::TileMode tileMode)
: INHERITED(1) {
// TODO: check for simple cases where we don't need a texture:
@@ -725,6 +749,8 @@ GrGradientEffect::GrGradientEffect(GrContext* ctx,
//shader.asAGradient(&info);
//if (info.fColorCount == 2) { ...
+ fMatrix = matrix;
+
SkBitmap bitmap;
shader.getGradientTableBitmap(&bitmap);
@@ -744,13 +770,13 @@ GrGradientEffect::GrGradientEffect(GrContext* ctx,
fRow = fAtlas->lockRow(bitmap);
if (-1 != fRow) {
- fYCoord = fAtlas->getYOffset(fRow) + GR_ScalarHalf *
+ fYCoord = fAtlas->getYOffset(fRow) + SK_ScalarHalf *
fAtlas->getVerticalScaleFactor();
fTextureAccess.reset(fAtlas->getTexture(), params);
} else {
GrTexture* texture = GrLockCachedBitmapTexture(ctx, bitmap, &params);
fTextureAccess.reset(texture, params);
- fYCoord = GR_ScalarHalf;
+ fYCoord = SK_ScalarHalf;
// Unlock immediately, this is not great, but we don't have a way of
// knowing when else to unlock it currently, so it may get purged from
@@ -781,7 +807,7 @@ int GrGradientEffect::RandomGradientParams(SkRandom* random,
*stops = NULL;
}
- GrScalar stop = 0.f;
+ SkScalar stop = 0.f;
for (int i = 0; i < outColors; ++i) {
colors[i] = random->nextU();
if (NULL != *stops) {
diff --git a/src/effects/gradients/SkGradientShaderPriv.h b/src/effects/gradients/SkGradientShaderPriv.h
index 61b9c35774..829d153d77 100644
--- a/src/effects/gradients/SkGradientShaderPriv.h
+++ b/src/effects/gradients/SkGradientShaderPriv.h
@@ -91,7 +91,6 @@ public:
int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper);
virtual ~SkGradientShaderBase();
- // overrides
virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&) SK_OVERRIDE;
virtual uint32_t getFlags() SK_OVERRIDE { return fFlags; }
virtual bool isOpaque() const SK_OVERRIDE;
@@ -192,13 +191,14 @@ private:
#if SK_SUPPORT_GPU
-#include "gl/GrGLProgramStage.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
-class GrSamplerState;
-class GrProgramStageFactory;
+class GrEffectStage;
+class GrBackendEffectFactory;
/*
- * The intepretation of the texture matrix depends on the sample mode. The
+ * The interpretation of the texture matrix depends on the sample mode. The
* texture matrix is applied both when the texture coordinates are explicit
* and when vertex positions are used as texture coordinates. In the latter
* case the texture matrix is applied to the pre-view-matrix position
@@ -223,11 +223,12 @@ class GrProgramStageFactory;
class GrTextureStripAtlas;
// Base class for Gr gradient effects
-class GrGradientEffect : public GrCustomStage {
+class GrGradientEffect : public GrEffect {
public:
GrGradientEffect(GrContext* ctx,
const SkGradientShaderBase& shader,
+ const SkMatrix& matrix,
SkShader::TileMode tileMode);
virtual ~GrGradientEffect();
@@ -235,12 +236,13 @@ public:
virtual const GrTextureAccess& textureAccess(int index) const SK_OVERRIDE;
bool useAtlas() const { return SkToBool(-1 != fRow); }
- GrScalar getYCoord() const { return fYCoord; };
+ SkScalar getYCoord() const { return fYCoord; };
+ const SkMatrix& getMatrix() const { return fMatrix;}
- virtual bool isEqual(const GrCustomStage& stage) const SK_OVERRIDE {
- const GrGradientEffect& s = static_cast<const GrGradientEffect&>(stage);
- return INHERITED::isEqual(stage) && this->useAtlas() == s.useAtlas() &&
- fYCoord == s.getYCoord();
+ virtual bool isEqual(const GrEffect& effect) const SK_OVERRIDE {
+ const GrGradientEffect& s = static_cast<const GrGradientEffect&>(effect);
+ return INHERITED::isEqual(effect) && this->useAtlas() == s.useAtlas() &&
+ fYCoord == s.getYCoord() && fMatrix.cheapEqualTo(s.getMatrix());
}
protected:
@@ -260,31 +262,63 @@ protected:
private:
GrTextureAccess fTextureAccess;
- GrScalar fYCoord;
+ SkScalar fYCoord;
GrTextureStripAtlas* fAtlas;
int fRow;
+ SkMatrix fMatrix;
- typedef GrCustomStage INHERITED;
+ typedef GrEffect INHERITED;
};
///////////////////////////////////////////////////////////////////////////////
-// Base class for GL gradient custom stages
-class GrGLGradientStage : public GrGLProgramStage {
+// Base class for GL gradient effects
+class GrGLGradientEffect : public GrGLEffect {
public:
+ GrGLGradientEffect(const GrBackendEffectFactory& factory);
+ virtual ~GrGLGradientEffect();
- GrGLGradientStage(const GrProgramStageFactory& factory);
- virtual ~GrGLGradientStage();
+ virtual void setData(const GrGLUniformManager&, const GrEffectStage&) SK_OVERRIDE;
- virtual void setupVariables(GrGLShaderBuilder* builder) SK_OVERRIDE;
- virtual void setData(const GrGLUniformManager&,
- const GrCustomStage&,
- const GrRenderTarget*,
- int stageNum) SK_OVERRIDE;
+protected:
+ /**
+ * Subclasses must reserve the lower kMatrixKeyBitCnt of their key for use by
+ * GrGLGradientEffect.
+ */
+ enum {
+ kMatrixKeyBitCnt = GrGLEffectMatrix::kKeyBits,
+ kMatrixKeyMask = (1 << kMatrixKeyBitCnt) - 1,
+ };
- // emit code that gets a fragment's color from an expression for t; for now
- // this always uses the texture, but for simpler cases we'll be able to lerp
+ /**
+ * Subclasses must call this. It will return a value restricted to the lower kMatrixKeyBitCnt
+ * bits.
+ */
+ static EffectKey GenMatrixKey(const GrEffectStage& s);
+
+ /**
+ * Inserts code to implement the GrGradientEffect's matrix. This should be called before a
+ * subclass emits its own code. The name of the 2D coords is output via fsCoordName and already
+ * incorporates any perspective division. The caller can also optionally retrieve the name of
+ * the varying inserted in the VS and its type, which may be either vec2f or vec3f depending
+ * upon whether the matrix has perspective or not. It is not necessary to mask the key before
+ * calling.
+ */
+ void setupMatrix(GrGLShaderBuilder* builder,
+ EffectKey key,
+ const char* vertexCoords,
+ const char** fsCoordName,
+ const char** vsVaryingName = NULL,
+ GrSLType* vsVaryingType = NULL);
+
+ // Emits the uniform used as the y-coord to texture samples in derived classes. Subclasses
+ // should call this method from their emitCode().
+ void emitYCoordUniform(GrGLShaderBuilder* builder);
+
+ // emit code that gets a fragment's color from an expression for t; for now this always uses the
+ // texture, but for simpler cases we'll be able to lerp. Subclasses should call this method from
+ // their emitCode().
void emitColorLookup(GrGLShaderBuilder* builder,
const char* gradientTValue,
const char* outputColor,
@@ -292,11 +326,11 @@ public:
const GrGLShaderBuilder::TextureSampler&);
private:
-
- GrScalar fCachedYCoord;
+ SkScalar fCachedYCoord;
GrGLUniformManager::UniformHandle fFSYUni;
+ GrGLEffectMatrix fEffectMatrix;
- typedef GrGLProgramStage INHERITED;
+ typedef GrGLEffect INHERITED;
};
#endif
diff --git a/src/effects/gradients/SkLinearGradient.cpp b/src/effects/gradients/SkLinearGradient.cpp
index 010f983b46..da881b62f4 100644
--- a/src/effects/gradients/SkLinearGradient.cpp
+++ b/src/effects/gradients/SkLinearGradient.cpp
@@ -474,28 +474,34 @@ void SkLinearGradient::shadeSpan16(int x, int y,
#if SK_SUPPORT_GPU
+#include "GrTBackendEffectFactory.h"
+
/////////////////////////////////////////////////////////////////////
-class GrGLLinearGradient : public GrGLGradientStage {
+class GrGLLinearGradient : public GrGLGradientEffect {
public:
- GrGLLinearGradient(const GrProgramStageFactory& factory,
- const GrCustomStage&)
+ GrGLLinearGradient(const GrBackendEffectFactory& factory,
+ const GrEffect&)
: INHERITED (factory) { }
virtual ~GrGLLinearGradient() { }
- virtual void emitVS(GrGLShaderBuilder* builder,
- const char* vertexCoords) SK_OVERRIDE { }
- virtual void emitFS(GrGLShaderBuilder* builder,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray&) SK_OVERRIDE;
- static StageKey GenKey(const GrCustomStage& s, const GrGLCaps& caps) { return 0; }
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrEffectStage&,
+ EffectKey,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ static EffectKey GenKey(const GrEffectStage& stage, const GrGLCaps&) {
+ return GenMatrixKey(stage);
+ }
private:
- typedef GrGLGradientStage INHERITED;
+ typedef GrGLGradientEffect INHERITED;
};
/////////////////////////////////////////////////////////////////////
@@ -503,30 +509,33 @@ private:
class GrLinearGradient : public GrGradientEffect {
public:
- GrLinearGradient(GrContext* ctx, const SkLinearGradient& shader, SkShader::TileMode tm)
- : INHERITED(ctx, shader, tm) { }
+ GrLinearGradient(GrContext* ctx,
+ const SkLinearGradient& shader,
+ const SkMatrix& matrix,
+ SkShader::TileMode tm)
+ : INHERITED(ctx, shader, matrix, tm) { }
virtual ~GrLinearGradient() { }
static const char* Name() { return "Linear Gradient"; }
- const GrProgramStageFactory& getFactory() const SK_OVERRIDE {
- return GrTProgramStageFactory<GrLinearGradient>::getInstance();
+ const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<GrLinearGradient>::getInstance();
}
- typedef GrGLLinearGradient GLProgramStage;
+ typedef GrGLLinearGradient GLEffect;
private:
- GR_DECLARE_CUSTOM_STAGE_TEST;
+ GR_DECLARE_EFFECT_TEST;
typedef GrGradientEffect INHERITED;
};
/////////////////////////////////////////////////////////////////////
-GR_DEFINE_CUSTOM_STAGE_TEST(GrLinearGradient);
+GR_DEFINE_EFFECT_TEST(GrLinearGradient);
-GrCustomStage* GrLinearGradient::TestCreate(SkRandom* random,
- GrContext* context,
- GrTexture**) {
+GrEffect* GrLinearGradient::TestCreate(SkRandom* random,
+ GrContext* context,
+ GrTexture**) {
SkPoint points[] = {{random->nextUScalar1(), random->nextUScalar1()},
{random->nextUScalar1(), random->nextUScalar1()}};
@@ -538,49 +547,48 @@ GrCustomStage* GrLinearGradient::TestCreate(SkRandom* random,
SkAutoTUnref<SkShader> shader(SkGradientShader::CreateLinear(points,
colors, stops, colorCount,
tm));
- GrSamplerState sampler;
- shader->asNewCustomStage(context, &sampler);
- GrAssert(NULL != sampler.getCustomStage());
- // const_cast and ref is a hack! Will remove when asNewCustomStage returns GrCustomStage*
- sampler.getCustomStage()->ref();
- return const_cast<GrCustomStage*>(sampler.getCustomStage());
+ GrEffectStage stage;
+ shader->asNewEffect(context, &stage);
+ GrAssert(NULL != stage.getEffect());
+ // const_cast and ref is a hack! Will remove when asNewEffect returns GrEffect*
+ stage.getEffect()->ref();
+ return const_cast<GrEffect*>(stage.getEffect());
}
/////////////////////////////////////////////////////////////////////
-void GrGLLinearGradient::emitFS(GrGLShaderBuilder* builder,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray& samplers) {
+void GrGLLinearGradient::emitCode(GrGLShaderBuilder* builder,
+ const GrEffectStage& stage,
+ EffectKey key,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ this->emitYCoordUniform(builder);
+ const char* coords;
+ this->setupMatrix(builder, key, vertexCoords, &coords);
SkString t;
- t.printf("%s.x", builder->defaultTexCoordsName());
+ t.append(coords);
+ t.append(".x");
this->emitColorLookup(builder, t.c_str(), outputColor, inputColor, samplers[0]);
}
/////////////////////////////////////////////////////////////////////
-bool SkLinearGradient::asNewCustomStage(GrContext* context, GrSamplerState* sampler) const {
- SkASSERT(NULL != context && NULL != sampler);
-
- SkAutoTUnref<GrCustomStage> stage(SkNEW_ARGS(GrLinearGradient, (context, *this, fTileMode)));
-
+bool SkLinearGradient::asNewEffect(GrContext* context, GrEffectStage* stage) const {
+ SkASSERT(NULL != context && NULL != stage);
SkMatrix matrix;
- if (this->getLocalMatrix(&matrix)) {
- if (!matrix.invert(&matrix)) {
- return false;
- }
- matrix.postConcat(fPtsToUnit);
- sampler->setCustomStage(stage, matrix);
- } else {
- sampler->setCustomStage(stage, fPtsToUnit);
+ if (!this->getLocalMatrix().invert(&matrix)) {
+ return false;
}
-
+ matrix.postConcat(fPtsToUnit);
+ stage->setEffect(SkNEW_ARGS(GrLinearGradient, (context, *this, matrix, fTileMode)))->unref();
return true;
}
#else
-bool SkLinearGradient::asNewCustomStage(GrContext*, GrSamplerState*) const {
+bool SkLinearGradient::asNewEffect(GrContext*, GrEffectStage*) const {
SkDEBUGFAIL("Should not call in GPU-less build");
return false;
}
diff --git a/src/effects/gradients/SkLinearGradient.h b/src/effects/gradients/SkLinearGradient.h
index fe60543d52..7d879266b1 100644
--- a/src/effects/gradients/SkLinearGradient.h
+++ b/src/effects/gradients/SkLinearGradient.h
@@ -22,7 +22,7 @@ public:
virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
virtual BitmapType asABitmap(SkBitmap*, SkMatrix*, TileMode*) const SK_OVERRIDE;
virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE;
- virtual bool asNewCustomStage(GrContext* context, GrSamplerState* sampler) const SK_OVERRIDE;
+ virtual bool asNewEffect(GrContext* context, GrEffectStage* stage) const SK_OVERRIDE;
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLinearGradient)
diff --git a/src/effects/gradients/SkRadialGradient.cpp b/src/effects/gradients/SkRadialGradient.cpp
index 0156acdd54..02a56da588 100644
--- a/src/effects/gradients/SkRadialGradient.cpp
+++ b/src/effects/gradients/SkRadialGradient.cpp
@@ -473,25 +473,30 @@ void SkRadialGradient::shadeSpan(int x, int y,
#if SK_SUPPORT_GPU
-class GrGLRadialGradient : public GrGLGradientStage {
+#include "GrTBackendEffectFactory.h"
+
+class GrGLRadialGradient : public GrGLGradientEffect {
public:
- GrGLRadialGradient(const GrProgramStageFactory& factory,
- const GrCustomStage&) : INHERITED (factory) { }
+ GrGLRadialGradient(const GrBackendEffectFactory& factory,
+ const GrEffect&) : INHERITED (factory) { }
virtual ~GrGLRadialGradient() { }
- virtual void emitVS(GrGLShaderBuilder* builder,
- const char* vertexCoords) SK_OVERRIDE { }
- virtual void emitFS(GrGLShaderBuilder* builder,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray&) SK_OVERRIDE;
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrEffectStage&,
+ EffectKey,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
- static StageKey GenKey(const GrCustomStage& s, const GrGLCaps& caps) { return 0; }
+ static EffectKey GenKey(const GrEffectStage& stage, const GrGLCaps&) {
+ return GenMatrixKey(stage);
+ }
private:
- typedef GrGLGradientStage INHERITED;
+ typedef GrGLGradientEffect INHERITED;
};
@@ -500,32 +505,35 @@ private:
class GrRadialGradient : public GrGradientEffect {
public:
- GrRadialGradient(GrContext* ctx, const SkRadialGradient& shader, SkShader::TileMode tm)
- : INHERITED(ctx, shader, tm) {
+ GrRadialGradient(GrContext* ctx,
+ const SkRadialGradient& shader,
+ const SkMatrix& matrix,
+ SkShader::TileMode tm)
+ : INHERITED(ctx, shader, matrix, tm) {
}
virtual ~GrRadialGradient() { }
static const char* Name() { return "Radial Gradient"; }
- virtual const GrProgramStageFactory& getFactory() const SK_OVERRIDE {
- return GrTProgramStageFactory<GrRadialGradient>::getInstance();
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<GrRadialGradient>::getInstance();
}
- typedef GrGLRadialGradient GLProgramStage;
+ typedef GrGLRadialGradient GLEffect;
private:
- GR_DECLARE_CUSTOM_STAGE_TEST;
+ GR_DECLARE_EFFECT_TEST;
typedef GrGradientEffect INHERITED;
};
/////////////////////////////////////////////////////////////////////
-GR_DEFINE_CUSTOM_STAGE_TEST(GrRadialGradient);
+GR_DEFINE_EFFECT_TEST(GrRadialGradient);
-GrCustomStage* GrRadialGradient::TestCreate(SkRandom* random,
- GrContext* context,
- GrTexture**) {
+GrEffect* GrRadialGradient::TestCreate(SkRandom* random,
+ GrContext* context,
+ GrTexture**) {
SkPoint center = {random->nextUScalar1(), random->nextUScalar1()};
SkScalar radius = random->nextUScalar1();
@@ -537,48 +545,49 @@ GrCustomStage* GrRadialGradient::TestCreate(SkRandom* random,
SkAutoTUnref<SkShader> shader(SkGradientShader::CreateRadial(center, radius,
colors, stops, colorCount,
tm));
- GrSamplerState sampler;
- shader->asNewCustomStage(context, &sampler);
- GrAssert(NULL != sampler.getCustomStage());
- // const_cast and ref is a hack! Will remove when asNewCustomStage returns GrCustomStage*
- sampler.getCustomStage()->ref();
- return const_cast<GrCustomStage*>(sampler.getCustomStage());
+ GrEffectStage stage;
+ shader->asNewEffect(context, &stage);
+ GrAssert(NULL != stage.getEffect());
+ // const_cast and ref is a hack! Will remove when asNewEffect returns GrEffect*
+ stage.getEffect()->ref();
+ return const_cast<GrEffect*>(stage.getEffect());
}
/////////////////////////////////////////////////////////////////////
-void GrGLRadialGradient::emitFS(GrGLShaderBuilder* builder,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray& samplers) {
- SkString t;
- t.printf("length(%s.xy)", builder->defaultTexCoordsName());
+void GrGLRadialGradient::emitCode(GrGLShaderBuilder* builder,
+ const GrEffectStage& stage,
+ EffectKey key,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ this->emitYCoordUniform(builder);
+ const char* coords;
+ this->setupMatrix(builder, key, vertexCoords, &coords);
+ SkString t("length(");
+ t.append(coords);
+ t.append(")");
this->emitColorLookup(builder, t.c_str(), outputColor, inputColor, samplers[0]);
}
/////////////////////////////////////////////////////////////////////
-bool SkRadialGradient::asNewCustomStage(GrContext* context, GrSamplerState* sampler) const {
- SkASSERT(NULL != context && NULL != sampler);
- SkAutoTUnref<GrCustomStage> stage(SkNEW_ARGS(GrRadialGradient, (context, *this, fTileMode)));
+bool SkRadialGradient::asNewEffect(GrContext* context, GrEffectStage* stage) const {
+ SkASSERT(NULL != context && NULL != stage);
SkMatrix matrix;
- if (this->getLocalMatrix(&matrix)) {
- if (!matrix.invert(&matrix)) {
- return false;
- }
- matrix.postConcat(fPtsToUnit);
- sampler->setCustomStage(stage, matrix);
- } else {
- sampler->setCustomStage(stage, fPtsToUnit);
+ if (!this->getLocalMatrix().invert(&matrix)) {
+ return false;
}
-
+ matrix.postConcat(fPtsToUnit);
+ stage->setEffect(SkNEW_ARGS(GrRadialGradient, (context, *this, matrix, fTileMode)))->unref();
return true;
}
#else
-bool SkRadialGradient::asNewCustomStage(GrContext*, GrSamplerState*) const {
+bool SkRadialGradient::asNewEffect(GrContext*, GrEffectStage*) const {
SkDEBUGFAIL("Should not call in GPU-less build");
return false;
}
diff --git a/src/effects/gradients/SkRadialGradient.h b/src/effects/gradients/SkRadialGradient.h
index fc17520e29..cf0d43d3b9 100644
--- a/src/effects/gradients/SkRadialGradient.h
+++ b/src/effects/gradients/SkRadialGradient.h
@@ -24,7 +24,7 @@ public:
SkMatrix* matrix,
TileMode* xy) const SK_OVERRIDE;
virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE;
- virtual bool asNewCustomStage(GrContext* context, GrSamplerState* sampler) const SK_OVERRIDE;
+ virtual bool asNewEffect(GrContext* context, GrEffectStage* stage) const SK_OVERRIDE;
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkRadialGradient)
diff --git a/src/effects/gradients/SkSweepGradient.cpp b/src/effects/gradients/SkSweepGradient.cpp
index 9fb51760e7..589cf4ad14 100644
--- a/src/effects/gradients/SkSweepGradient.cpp
+++ b/src/effects/gradients/SkSweepGradient.cpp
@@ -382,25 +382,30 @@ void SkSweepGradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC,
#if SK_SUPPORT_GPU
-class GrGLSweepGradient : public GrGLGradientStage {
+#include "GrTBackendEffectFactory.h"
+
+class GrGLSweepGradient : public GrGLGradientEffect {
public:
- GrGLSweepGradient(const GrProgramStageFactory& factory,
- const GrCustomStage&) : INHERITED (factory) { }
+ GrGLSweepGradient(const GrBackendEffectFactory& factory,
+ const GrEffect&) : INHERITED (factory) { }
virtual ~GrGLSweepGradient() { }
- virtual void emitVS(GrGLShaderBuilder* builder,
- const char* vertexCoords) SK_OVERRIDE { }
- virtual void emitFS(GrGLShaderBuilder* builder,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray&) SK_OVERRIDE;
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrEffectStage&,
+ EffectKey,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
- static StageKey GenKey(const GrCustomStage& s, const GrGLCaps& caps) { return 0; }
+ static EffectKey GenKey(const GrEffectStage& stage, const GrGLCaps&) {
+ return GenMatrixKey(stage);
+ }
private:
- typedef GrGLGradientStage INHERITED;
+ typedef GrGLGradientEffect INHERITED;
};
@@ -410,30 +415,31 @@ class GrSweepGradient : public GrGradientEffect {
public:
GrSweepGradient(GrContext* ctx,
- const SkSweepGradient& shader)
- : INHERITED(ctx, shader, SkShader::kClamp_TileMode) { }
+ const SkSweepGradient& shader,
+ const SkMatrix& matrix)
+ : INHERITED(ctx, shader, matrix, SkShader::kClamp_TileMode) { }
virtual ~GrSweepGradient() { }
static const char* Name() { return "Sweep Gradient"; }
- virtual const GrProgramStageFactory& getFactory() const SK_OVERRIDE {
- return GrTProgramStageFactory<GrSweepGradient>::getInstance();
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<GrSweepGradient>::getInstance();
}
- typedef GrGLSweepGradient GLProgramStage;
+ typedef GrGLSweepGradient GLEffect;
private:
- GR_DECLARE_CUSTOM_STAGE_TEST;
+ GR_DECLARE_EFFECT_TEST;
typedef GrGradientEffect INHERITED;
};
/////////////////////////////////////////////////////////////////////
-GR_DEFINE_CUSTOM_STAGE_TEST(GrSweepGradient);
+GR_DEFINE_EFFECT_TEST(GrSweepGradient);
-GrCustomStage* GrSweepGradient::TestCreate(SkRandom* random,
- GrContext* context,
- GrTexture**) {
+GrEffect* GrSweepGradient::TestCreate(SkRandom* random,
+ GrContext* context,
+ GrTexture**) {
SkPoint center = {random->nextUScalar1(), random->nextUScalar1()};
SkColor colors[kMaxRandomGradientColors];
@@ -443,49 +449,46 @@ GrCustomStage* GrSweepGradient::TestCreate(SkRandom* random,
int colorCount = RandomGradientParams(random, colors, &stops, &tmIgnored);
SkAutoTUnref<SkShader> shader(SkGradientShader::CreateSweep(center.fX, center.fY,
colors, stops, colorCount));
- GrSamplerState sampler;
- shader->asNewCustomStage(context, &sampler);
- GrAssert(NULL != sampler.getCustomStage());
- // const_cast and ref is a hack! Will remove when asNewCustomStage returns GrCustomStage*
- sampler.getCustomStage()->ref();
- return const_cast<GrCustomStage*>(sampler.getCustomStage());
+ GrEffectStage stage;
+ shader->asNewEffect(context, &stage);
+ GrAssert(NULL != stage.getEffect());
+ // const_cast and ref is a hack! Will remove when asNewEffect returns GrEffect*
+ stage.getEffect()->ref();
+ return const_cast<GrEffect*>(stage.getEffect());
}
/////////////////////////////////////////////////////////////////////
-void GrGLSweepGradient::emitFS(GrGLShaderBuilder* builder,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray& samplers) {
+void GrGLSweepGradient::emitCode(GrGLShaderBuilder* builder,
+ const GrEffectStage& stage,
+ EffectKey key,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ this->emitYCoordUniform(builder);
+ const char* coords;
+ this->setupMatrix(builder, key, vertexCoords, &coords);
SkString t;
- t.printf("atan(- %s.y, - %s.x) * 0.1591549430918 + 0.5",
- builder->defaultTexCoordsName(), builder->defaultTexCoordsName());
+ t.printf("atan(- %s.y, - %s.x) * 0.1591549430918 + 0.5", coords, coords);
this->emitColorLookup(builder, t.c_str(), outputColor, inputColor, samplers[0]);
}
/////////////////////////////////////////////////////////////////////
-bool SkSweepGradient::asNewCustomStage(GrContext* context, GrSamplerState* sampler) const {
- SkAutoTUnref<GrCustomStage> stage(SkNEW_ARGS(GrSweepGradient, (context, *this)));
-
-
+bool SkSweepGradient::asNewEffect(GrContext* context, GrEffectStage* stage) const {
SkMatrix matrix;
- if (this->getLocalMatrix(&matrix)) {
- if (!matrix.invert(&matrix)) {
- return false;
- }
- matrix.postConcat(fPtsToUnit);
- sampler->setCustomStage(stage, matrix);
- } else {
- sampler->setCustomStage(stage, fPtsToUnit);
+ if (!this->getLocalMatrix().invert(&matrix)) {
+ return false;
}
-
+ matrix.postConcat(fPtsToUnit);
+ stage->setEffect(SkNEW_ARGS(GrSweepGradient, (context, *this, matrix)))->unref();
return true;
}
#else
-bool SkSweepGradient::asNewCustomStage(GrContext*, GrSamplerState*) const {
+bool SkSweepGradient::asNewEffect(GrContext*, GrEffectStage*) const {
SkDEBUGFAIL("Should not call in GPU-less build");
return false;
}
diff --git a/src/effects/gradients/SkSweepGradient.h b/src/effects/gradients/SkSweepGradient.h
index 8e42be0921..a44b4c15f1 100644
--- a/src/effects/gradients/SkSweepGradient.h
+++ b/src/effects/gradients/SkSweepGradient.h
@@ -24,7 +24,7 @@ public:
virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE;
- virtual bool asNewCustomStage(GrContext* context, GrSamplerState* sampler) const SK_OVERRIDE;
+ virtual bool asNewEffect(GrContext* context, GrEffectStage* stage) const SK_OVERRIDE;
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkSweepGradient)
diff --git a/src/effects/gradients/SkTwoPointConicalGradient.cpp b/src/effects/gradients/SkTwoPointConicalGradient.cpp
index 19b1228c88..5c06bce45c 100644
--- a/src/effects/gradients/SkTwoPointConicalGradient.cpp
+++ b/src/effects/gradients/SkTwoPointConicalGradient.cpp
@@ -315,30 +315,29 @@ void SkTwoPointConicalGradient::flatten(
#if SK_SUPPORT_GPU
+#include "GrTBackendEffectFactory.h"
+
// For brevity
typedef GrGLUniformManager::UniformHandle UniformHandle;
static const UniformHandle kInvalidUniformHandle = GrGLUniformManager::kInvalidUniformHandle;
-class GrGLConical2Gradient : public GrGLGradientStage {
+class GrGLConical2Gradient : public GrGLGradientEffect {
public:
- GrGLConical2Gradient(const GrProgramStageFactory& factory,
- const GrCustomStage&);
+ GrGLConical2Gradient(const GrBackendEffectFactory& factory,
+ const GrEffect&);
virtual ~GrGLConical2Gradient() { }
- virtual void setupVariables(GrGLShaderBuilder* builder) SK_OVERRIDE;
- virtual void emitVS(GrGLShaderBuilder* builder,
- const char* vertexCoords) SK_OVERRIDE;
- virtual void emitFS(GrGLShaderBuilder* builder,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray&) SK_OVERRIDE;
- virtual void setData(const GrGLUniformManager&,
- const GrCustomStage&,
- const GrRenderTarget*,
- int stageNum) SK_OVERRIDE;
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrEffectStage&,
+ EffectKey,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+ virtual void setData(const GrGLUniformManager&, const GrEffectStage&) SK_OVERRIDE;
- static StageKey GenKey(const GrCustomStage& s, const GrGLCaps& caps);
+ static EffectKey GenKey(const GrEffectStage&, const GrGLCaps& caps);
protected:
@@ -353,15 +352,15 @@ protected:
// @{
/// Values last uploaded as uniforms
- GrScalar fCachedCenter;
- GrScalar fCachedRadius;
- GrScalar fCachedDiffRadius;
+ SkScalar fCachedCenter;
+ SkScalar fCachedRadius;
+ SkScalar fCachedDiffRadius;
// @}
private:
- typedef GrGLGradientStage INHERITED;
+ typedef GrGLGradientEffect INHERITED;
};
@@ -372,8 +371,9 @@ public:
GrConical2Gradient(GrContext* ctx,
const SkTwoPointConicalGradient& shader,
+ const SkMatrix& matrix,
SkShader::TileMode tm)
- : INHERITED(ctx, shader, tm)
+ : INHERITED(ctx, shader, matrix, tm)
, fCenterX1(shader.getCenterX1())
, fRadius0(shader.getStartRadius())
, fDiffRadius(shader.getDiffRadius()) { }
@@ -381,10 +381,10 @@ public:
virtual ~GrConical2Gradient() { }
static const char* Name() { return "Two-Point Conical Gradient"; }
- virtual const GrProgramStageFactory& getFactory() const SK_OVERRIDE {
- return GrTProgramStageFactory<GrConical2Gradient>::getInstance();
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<GrConical2Gradient>::getInstance();
}
- virtual bool isEqual(const GrCustomStage& sBase) const SK_OVERRIDE {
+ virtual bool isEqual(const GrEffect& sBase) const SK_OVERRIDE {
const GrConical2Gradient& s = static_cast<const GrConical2Gradient&>(sBase);
return (INHERITED::isEqual(sBase) &&
this->fCenterX1 == s.fCenterX1 &&
@@ -394,39 +394,39 @@ public:
// The radial gradient parameters can collapse to a linear (instead of quadratic) equation.
bool isDegenerate() const { return SkScalarAbs(fDiffRadius) == SkScalarAbs(fCenterX1); }
- GrScalar center() const { return fCenterX1; }
- GrScalar diffRadius() const { return fDiffRadius; }
- GrScalar radius() const { return fRadius0; }
+ SkScalar center() const { return fCenterX1; }
+ SkScalar diffRadius() const { return fDiffRadius; }
+ SkScalar radius() const { return fRadius0; }
- typedef GrGLConical2Gradient GLProgramStage;
+ typedef GrGLConical2Gradient GLEffect;
private:
- GR_DECLARE_CUSTOM_STAGE_TEST;
+ GR_DECLARE_EFFECT_TEST;
// @{
// Cache of values - these can change arbitrarily, EXCEPT
// we shouldn't change between degenerate and non-degenerate?!
- GrScalar fCenterX1;
- GrScalar fRadius0;
- GrScalar fDiffRadius;
+ SkScalar fCenterX1;
+ SkScalar fRadius0;
+ SkScalar fDiffRadius;
// @}
typedef GrGradientEffect INHERITED;
};
-GR_DEFINE_CUSTOM_STAGE_TEST(GrConical2Gradient);
+GR_DEFINE_EFFECT_TEST(GrConical2Gradient);
-GrCustomStage* GrConical2Gradient::TestCreate(SkRandom* random,
- GrContext* context,
- GrTexture**) {
+GrEffect* GrConical2Gradient::TestCreate(SkRandom* random,
+ GrContext* context,
+ GrTexture**) {
SkPoint center1 = {random->nextUScalar1(), random->nextUScalar1()};
SkScalar radius1 = random->nextUScalar1();
SkPoint center2;
SkScalar radius2;
do {
- center1.set(random->nextUScalar1(), random->nextUScalar1());
+ center2.set(random->nextUScalar1(), random->nextUScalar1());
radius2 = random->nextUScalar1 ();
// If the circles are identical the factory will give us an empty shader.
} while (radius1 == radius2 && center1 == center2);
@@ -440,36 +440,47 @@ GrCustomStage* GrConical2Gradient::TestCreate(SkRandom* random,
center2, radius2,
colors, stops, colorCount,
tm));
- GrSamplerState sampler;
- shader->asNewCustomStage(context, &sampler);
- GrAssert(NULL != sampler.getCustomStage());
- // const_cast and ref is a hack! Will remove when asNewCustomStage returns GrCustomStage*
- sampler.getCustomStage()->ref();
- return const_cast<GrCustomStage*>(sampler.getCustomStage());
+ GrEffectStage stage;
+ shader->asNewEffect(context, &stage);
+ GrAssert(NULL != stage.getEffect());
+ // const_cast and ref is a hack! Will remove when asNewEffect returns GrEffect*
+ stage.getEffect()->ref();
+ return const_cast<GrEffect*>(stage.getEffect());
}
/////////////////////////////////////////////////////////////////////
GrGLConical2Gradient::GrGLConical2Gradient(
- const GrProgramStageFactory& factory,
- const GrCustomStage& baseData)
+ const GrBackendEffectFactory& factory,
+ const GrEffect& baseData)
: INHERITED(factory)
, fVSParamUni(kInvalidUniformHandle)
, fFSParamUni(kInvalidUniformHandle)
, fVSVaryingName(NULL)
, fFSVaryingName(NULL)
- , fCachedCenter(GR_ScalarMax)
- , fCachedRadius(-GR_ScalarMax)
- , fCachedDiffRadius(-GR_ScalarMax) {
+ , fCachedCenter(SK_ScalarMax)
+ , fCachedRadius(-SK_ScalarMax)
+ , fCachedDiffRadius(-SK_ScalarMax) {
const GrConical2Gradient& data =
static_cast<const GrConical2Gradient&>(baseData);
fIsDegenerate = data.isDegenerate();
}
-void GrGLConical2Gradient::setupVariables(GrGLShaderBuilder* builder) {
- INHERITED::setupVariables(builder);
+void GrGLConical2Gradient::emitCode(GrGLShaderBuilder* builder,
+ const GrEffectStage& stage,
+ EffectKey key,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ const char* fsCoords;
+ const char* vsCoordsVarying;
+ GrSLType coordsVaryingType;
+ this->setupMatrix(builder, key, vertexCoords, &fsCoords, &vsCoordsVarying, &coordsVaryingType);
+
+ this->emitYCoordUniform(builder);
// 2 copies of uniform array, 1 for each of vertex & fragment shader,
// to work around Xoom bug. Doesn't seem to cause performance decrease
// in test apps, but need to keep an eye on it.
@@ -480,172 +491,166 @@ void GrGLConical2Gradient::setupVariables(GrGLShaderBuilder* builder) {
// For radial gradients without perspective we can pass the linear
// part of the quadratic as a varying.
- if (!builder->defaultTextureMatrixIsPerspective()) {
+ if (kVec2f_GrSLType == coordsVaryingType) {
builder->addVarying(kFloat_GrSLType, "Conical2BCoeff",
&fVSVaryingName, &fFSVaryingName);
}
-}
-void GrGLConical2Gradient::emitVS(GrGLShaderBuilder* builder,
- const char* vertexCoords) {
- SkString* code = &builder->fVSCode;
- SkString p2; // distance between centers
- SkString p3; // start radius
- SkString p5; // difference in radii (r1 - r0)
- builder->getUniformVariable(fVSParamUni).appendArrayAccess(2, &p2);
- builder->getUniformVariable(fVSParamUni).appendArrayAccess(3, &p3);
- builder->getUniformVariable(fVSParamUni).appendArrayAccess(5, &p5);
-
- // For radial gradients without perspective we can pass the linear
- // part of the quadratic as a varying.
- if (!builder->defaultTextureMatrixIsPerspective()) {
- // r2Var = -2 * (r2Parm[2] * varCoord.x - r2Param[3] * r2Param[5])
- code->appendf("\t%s = -2.0 * (%s * %s.x + %s * %s);\n",
- fVSVaryingName, p2.c_str(),
- vertexCoords, p3.c_str(), p5.c_str());
+ // VS
+ {
+ SkString* code = &builder->fVSCode;
+ SkString p2; // distance between centers
+ SkString p3; // start radius
+ SkString p5; // difference in radii (r1 - r0)
+ builder->getUniformVariable(fVSParamUni).appendArrayAccess(2, &p2);
+ builder->getUniformVariable(fVSParamUni).appendArrayAccess(3, &p3);
+ builder->getUniformVariable(fVSParamUni).appendArrayAccess(5, &p5);
+
+ // For radial gradients without perspective we can pass the linear
+ // part of the quadratic as a varying.
+ if (kVec2f_GrSLType == coordsVaryingType) {
+ // r2Var = -2 * (r2Parm[2] * varCoord.x - r2Param[3] * r2Param[5])
+ code->appendf("\t%s = -2.0 * (%s * %s.x + %s * %s);\n",
+ fVSVaryingName, p2.c_str(),
+ vsCoordsVarying, p3.c_str(), p5.c_str());
+ }
}
-}
-void GrGLConical2Gradient::emitFS(GrGLShaderBuilder* builder,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray& samplers) {
- SkString* code = &builder->fFSCode;
-
- SkString cName("c");
- SkString ac4Name("ac4");
- SkString dName("d");
- SkString qName("q");
- SkString r0Name("r0");
- SkString r1Name("r1");
- SkString tName("t");
- SkString p0; // 4a
- SkString p1; // 1/a
- SkString p2; // distance between centers
- SkString p3; // start radius
- SkString p4; // start radius squared
- SkString p5; // difference in radii (r1 - r0)
-
- builder->getUniformVariable(fFSParamUni).appendArrayAccess(0, &p0);
- builder->getUniformVariable(fFSParamUni).appendArrayAccess(1, &p1);
- builder->getUniformVariable(fFSParamUni).appendArrayAccess(2, &p2);
- builder->getUniformVariable(fFSParamUni).appendArrayAccess(3, &p3);
- builder->getUniformVariable(fFSParamUni).appendArrayAccess(4, &p4);
- builder->getUniformVariable(fFSParamUni).appendArrayAccess(5, &p5);
-
- // If we we're able to interpolate the linear component,
- // bVar is the varying; otherwise compute it
- SkString bVar;
- if (!builder->defaultTextureMatrixIsPerspective()) {
- bVar = fFSVaryingName;
- } else {
- bVar = "b";
- code->appendf("\tfloat %s = -2.0 * (%s * %s.x + %s * %s);\n",
- bVar.c_str(), p2.c_str(), builder->defaultTexCoordsName(),
- p3.c_str(), p5.c_str());
- }
+ // FS
+ {
+ SkString* code = &builder->fFSCode;
+
+ SkString cName("c");
+ SkString ac4Name("ac4");
+ SkString dName("d");
+ SkString qName("q");
+ SkString r0Name("r0");
+ SkString r1Name("r1");
+ SkString tName("t");
+ SkString p0; // 4a
+ SkString p1; // 1/a
+ SkString p2; // distance between centers
+ SkString p3; // start radius
+ SkString p4; // start radius squared
+ SkString p5; // difference in radii (r1 - r0)
+
+ builder->getUniformVariable(fFSParamUni).appendArrayAccess(0, &p0);
+ builder->getUniformVariable(fFSParamUni).appendArrayAccess(1, &p1);
+ builder->getUniformVariable(fFSParamUni).appendArrayAccess(2, &p2);
+ builder->getUniformVariable(fFSParamUni).appendArrayAccess(3, &p3);
+ builder->getUniformVariable(fFSParamUni).appendArrayAccess(4, &p4);
+ builder->getUniformVariable(fFSParamUni).appendArrayAccess(5, &p5);
+
+ // If we we're able to interpolate the linear component,
+ // bVar is the varying; otherwise compute it
+ SkString bVar;
+ if (kVec2f_GrSLType == coordsVaryingType) {
+ bVar = fFSVaryingName;
+ } else {
+ bVar = "b";
+ code->appendf("\tfloat %s = -2.0 * (%s * %s.x + %s * %s);\n",
+ bVar.c_str(), p2.c_str(), fsCoords,
+ p3.c_str(), p5.c_str());
+ }
- // output will default to transparent black (we simply won't write anything
- // else to it if invalid, instead of discarding or returning prematurely)
- code->appendf("\t%s = vec4(0.0,0.0,0.0,0.0);\n", outputColor);
-
- // c = (x^2)+(y^2) - params[4]
- code->appendf("\tfloat %s = dot(%s, %s) - %s;\n", cName.c_str(),
- builder->defaultTexCoordsName(), builder->defaultTexCoordsName(),
- p4.c_str());
-
- // Non-degenerate case (quadratic)
- if (!fIsDegenerate) {
-
- // ac4 = params[0] * c
- code->appendf("\tfloat %s = %s * %s;\n", ac4Name.c_str(), p0.c_str(),
- cName.c_str());
-
- // d = b^2 - ac4
- code->appendf("\tfloat %s = %s * %s - %s;\n", dName.c_str(),
- bVar.c_str(), bVar.c_str(), ac4Name.c_str());
-
- // only proceed if discriminant is >= 0
- code->appendf("\tif (%s >= 0.0) {\n", dName.c_str());
-
- // intermediate value we'll use to compute the roots
- // q = -0.5 * (b +/- sqrt(d))
- code->appendf("\t\tfloat %s = -0.5 * (%s + (%s < 0.0 ? -1.0 : 1.0)"
- " * sqrt(%s));\n", qName.c_str(), bVar.c_str(),
- bVar.c_str(), dName.c_str());
-
- // compute both roots
- // r0 = q * params[1]
- code->appendf("\t\tfloat %s = %s * %s;\n", r0Name.c_str(),
- qName.c_str(), p1.c_str());
- // r1 = c / q
- code->appendf("\t\tfloat %s = %s / %s;\n", r1Name.c_str(),
- cName.c_str(), qName.c_str());
-
- // Note: If there are two roots that both generate radius(t) > 0, the
- // Canvas spec says to choose the larger t.
-
- // so we'll look at the larger one first:
- code->appendf("\t\tfloat %s = max(%s, %s);\n", tName.c_str(),
- r0Name.c_str(), r1Name.c_str());
-
- // if r(t) > 0, then we're done; t will be our x coordinate
- code->appendf("\t\tif (%s * %s + %s > 0.0) {\n", tName.c_str(),
- p5.c_str(), p3.c_str());
-
- code->appendf("\t\t");
- this->emitColorLookup(builder, tName.c_str(), outputColor, inputColor, samplers[0]);
-
- // otherwise, if r(t) for the larger root was <= 0, try the other root
- code->appendf("\t\t} else {\n");
- code->appendf("\t\t\t%s = min(%s, %s);\n", tName.c_str(),
- r0Name.c_str(), r1Name.c_str());
-
- // if r(t) > 0 for the smaller root, then t will be our x coordinate
- code->appendf("\t\t\tif (%s * %s + %s > 0.0) {\n",
- tName.c_str(), p5.c_str(), p3.c_str());
-
- code->appendf("\t\t\t");
- this->emitColorLookup(builder, tName.c_str(), outputColor, inputColor, samplers[0]);
-
- // end if (r(t) > 0) for smaller root
- code->appendf("\t\t\t}\n");
- // end if (r(t) > 0), else, for larger root
- code->appendf("\t\t}\n");
- // end if (discriminant >= 0)
- code->appendf("\t}\n");
- } else {
+ // output will default to transparent black (we simply won't write anything
+ // else to it if invalid, instead of discarding or returning prematurely)
+ code->appendf("\t%s = vec4(0.0,0.0,0.0,0.0);\n", outputColor);
+
+ // c = (x^2)+(y^2) - params[4]
+ code->appendf("\tfloat %s = dot(%s, %s) - %s;\n", cName.c_str(),
+ fsCoords, fsCoords,
+ p4.c_str());
+
+ // Non-degenerate case (quadratic)
+ if (!fIsDegenerate) {
+
+ // ac4 = params[0] * c
+ code->appendf("\tfloat %s = %s * %s;\n", ac4Name.c_str(), p0.c_str(),
+ cName.c_str());
+
+ // d = b^2 - ac4
+ code->appendf("\tfloat %s = %s * %s - %s;\n", dName.c_str(),
+ bVar.c_str(), bVar.c_str(), ac4Name.c_str());
+
+ // only proceed if discriminant is >= 0
+ code->appendf("\tif (%s >= 0.0) {\n", dName.c_str());
+
+ // intermediate value we'll use to compute the roots
+ // q = -0.5 * (b +/- sqrt(d))
+ code->appendf("\t\tfloat %s = -0.5 * (%s + (%s < 0.0 ? -1.0 : 1.0)"
+ " * sqrt(%s));\n", qName.c_str(), bVar.c_str(),
+ bVar.c_str(), dName.c_str());
+
+ // compute both roots
+ // r0 = q * params[1]
+ code->appendf("\t\tfloat %s = %s * %s;\n", r0Name.c_str(),
+ qName.c_str(), p1.c_str());
+ // r1 = c / q
+ code->appendf("\t\tfloat %s = %s / %s;\n", r1Name.c_str(),
+ cName.c_str(), qName.c_str());
+
+ // Note: If there are two roots that both generate radius(t) > 0, the
+ // Canvas spec says to choose the larger t.
+
+ // so we'll look at the larger one first:
+ code->appendf("\t\tfloat %s = max(%s, %s);\n", tName.c_str(),
+ r0Name.c_str(), r1Name.c_str());
+
+ // if r(t) > 0, then we're done; t will be our x coordinate
+ code->appendf("\t\tif (%s * %s + %s > 0.0) {\n", tName.c_str(),
+ p5.c_str(), p3.c_str());
+
+ code->appendf("\t\t");
+ this->emitColorLookup(builder, tName.c_str(), outputColor, inputColor, samplers[0]);
+
+ // otherwise, if r(t) for the larger root was <= 0, try the other root
+ code->appendf("\t\t} else {\n");
+ code->appendf("\t\t\t%s = min(%s, %s);\n", tName.c_str(),
+ r0Name.c_str(), r1Name.c_str());
+
+ // if r(t) > 0 for the smaller root, then t will be our x coordinate
+ code->appendf("\t\t\tif (%s * %s + %s > 0.0) {\n",
+ tName.c_str(), p5.c_str(), p3.c_str());
+
+ code->appendf("\t\t\t");
+ this->emitColorLookup(builder, tName.c_str(), outputColor, inputColor, samplers[0]);
+
+ // end if (r(t) > 0) for smaller root
+ code->appendf("\t\t\t}\n");
+ // end if (r(t) > 0), else, for larger root
+ code->appendf("\t\t}\n");
+ // end if (discriminant >= 0)
+ code->appendf("\t}\n");
+ } else {
- // linear case: t = -c/b
- code->appendf("\tfloat %s = -(%s / %s);\n", tName.c_str(),
- cName.c_str(), bVar.c_str());
+ // linear case: t = -c/b
+ code->appendf("\tfloat %s = -(%s / %s);\n", tName.c_str(),
+ cName.c_str(), bVar.c_str());
- // if r(t) > 0, then t will be the x coordinate
- code->appendf("\tif (%s * %s + %s > 0.0) {\n", tName.c_str(),
- p5.c_str(), p3.c_str());
- code->appendf("\t");
- this->emitColorLookup(builder, tName.c_str(), outputColor, inputColor, samplers[0]);
- code->appendf("\t}\n");
+ // if r(t) > 0, then t will be the x coordinate
+ code->appendf("\tif (%s * %s + %s > 0.0) {\n", tName.c_str(),
+ p5.c_str(), p3.c_str());
+ code->appendf("\t");
+ this->emitColorLookup(builder, tName.c_str(), outputColor, inputColor, samplers[0]);
+ code->appendf("\t}\n");
+ }
}
}
-void GrGLConical2Gradient::setData(const GrGLUniformManager& uman,
- const GrCustomStage& baseData,
- const GrRenderTarget* target,
- int stageNum) {
- INHERITED::setData(uman, baseData, target, stageNum);
- const GrConical2Gradient& data =
- static_cast<const GrConical2Gradient&>(baseData);
+void GrGLConical2Gradient::setData(const GrGLUniformManager& uman, const GrEffectStage& stage) {
+ INHERITED::setData(uman, stage);
+ const GrConical2Gradient& data = static_cast<const GrConical2Gradient&>(*stage.getEffect());
GrAssert(data.isDegenerate() == fIsDegenerate);
- GrScalar centerX1 = data.center();
- GrScalar radius0 = data.radius();
- GrScalar diffRadius = data.diffRadius();
+ SkScalar centerX1 = data.center();
+ SkScalar radius0 = data.radius();
+ SkScalar diffRadius = data.diffRadius();
if (fCachedCenter != centerX1 ||
fCachedRadius != radius0 ||
fCachedDiffRadius != diffRadius) {
- GrScalar a = GrMul(centerX1, centerX1) - diffRadius * diffRadius;
+ SkScalar a = SkScalarMul(centerX1, centerX1) - diffRadius * diffRadius;
// When we're in the degenerate (linear) case, the second
// value will be INF but the program doesn't read it. (We
@@ -653,12 +658,12 @@ void GrGLConical2Gradient::setData(const GrGLUniformManager& uman,
// all in the linear case just to keep the code complexity
// down).
float values[6] = {
- GrScalarToFloat(a * 4),
- 1.f / (GrScalarToFloat(a)),
- GrScalarToFloat(centerX1),
- GrScalarToFloat(radius0),
- GrScalarToFloat(SkScalarMul(radius0, radius0)),
- GrScalarToFloat(diffRadius)
+ SkScalarToFloat(a * 4),
+ 1.f / (SkScalarToFloat(a)),
+ SkScalarToFloat(centerX1),
+ SkScalarToFloat(radius0),
+ SkScalarToFloat(SkScalarMul(radius0, radius0)),
+ SkScalarToFloat(diffRadius)
};
uman.set1fv(fVSParamUni, 0, 6, values);
@@ -669,44 +674,49 @@ void GrGLConical2Gradient::setData(const GrGLUniformManager& uman,
}
}
-GrCustomStage::StageKey GrGLConical2Gradient::GenKey(const GrCustomStage& s, const GrGLCaps& caps) {
- return (static_cast<const GrConical2Gradient&>(s).isDegenerate());
+GrGLEffect::EffectKey GrGLConical2Gradient::GenKey(const GrEffectStage& s, const GrGLCaps&) {
+ enum {
+ kIsDegenerate = 1 << kMatrixKeyBitCnt,
+ };
+
+ EffectKey key = GenMatrixKey(s);
+ if (static_cast<const GrConical2Gradient&>(*s.getEffect()).isDegenerate()) {
+ key |= kIsDegenerate;
+ }
+ return key;
}
/////////////////////////////////////////////////////////////////////
-bool SkTwoPointConicalGradient::asNewCustomStage(GrContext* context,
- GrSamplerState* sampler) const {
- SkASSERT(NULL != context && NULL != sampler);
-
+bool SkTwoPointConicalGradient::asNewEffect(GrContext* context,
+ GrEffectStage* stage) const {
+ SkASSERT(NULL != context && NULL != stage);
+ SkASSERT(fPtsToUnit.isIdentity());
+ // invert the localM, translate to center1, rotate so center2 is on x axis.
SkMatrix matrix;
+ if (!this->getLocalMatrix().invert(&matrix)) {
+ return false;
+ }
+ matrix.postTranslate(-fCenter1.fX, -fCenter1.fY);
+
SkPoint diff = fCenter2 - fCenter1;
SkScalar diffLen = diff.length();
if (0 != diffLen) {
SkScalar invDiffLen = SkScalarInvert(diffLen);
- matrix.setSinCos(-SkScalarMul(invDiffLen, diff.fY),
- SkScalarMul(invDiffLen, diff.fX));
- } else {
- matrix.reset();
- }
- matrix.preTranslate(-fCenter1.fX, -fCenter1.fY);
-
- SkMatrix localM;
- if (this->getLocalMatrix(&localM)) {
- if (!localM.invert(&localM)) {
- return false;
- }
- matrix.preConcat(localM);
+ SkMatrix rot;
+ rot.setSinCos(-SkScalarMul(invDiffLen, diff.fY),
+ SkScalarMul(invDiffLen, diff.fX));
+ matrix.postConcat(rot);
}
- sampler->setCustomStage(SkNEW_ARGS(GrConical2Gradient, (context, *this, fTileMode)), matrix)->unref();
+ stage->setEffect(SkNEW_ARGS(GrConical2Gradient, (context, *this, matrix, fTileMode)))->unref();
return true;
}
#else
-bool SkTwoPointConicalGradient::asNewCustomStage(GrContext*, GrSamplerState*) const {
+bool SkTwoPointConicalGradient::asNewEffect(GrContext*, GrEffectStage*) const {
SkDEBUGFAIL("Should not call in GPU-less build");
return false;
}
diff --git a/src/effects/gradients/SkTwoPointConicalGradient.h b/src/effects/gradients/SkTwoPointConicalGradient.h
index 40544919c3..d199650414 100644
--- a/src/effects/gradients/SkTwoPointConicalGradient.h
+++ b/src/effects/gradients/SkTwoPointConicalGradient.h
@@ -61,7 +61,7 @@ public:
SkMatrix* matrix,
TileMode* xy) const;
virtual SkShader::GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE;
- virtual bool asNewCustomStage(GrContext* context, GrSamplerState* sampler) const SK_OVERRIDE;
+ virtual bool asNewEffect(GrContext* context, GrEffectStage* stage) const SK_OVERRIDE;
SkScalar getCenterX1() const { return SkPoint::Distance(fCenter1, fCenter2); }
SkScalar getStartRadius() const { return fRadius1; }
diff --git a/src/effects/gradients/SkTwoPointRadialGradient.cpp b/src/effects/gradients/SkTwoPointRadialGradient.cpp
index 2715511e1b..9aa923b2db 100644
--- a/src/effects/gradients/SkTwoPointRadialGradient.cpp
+++ b/src/effects/gradients/SkTwoPointRadialGradient.cpp
@@ -292,16 +292,15 @@ void SkTwoPointRadialGradient::shadeSpan(int x, int y, SkPMColor* dstCParam,
}
}
-bool SkTwoPointRadialGradient::setContext(
- const SkBitmap& device,
- const SkPaint& paint,
- const SkMatrix& matrix){
- if (!this->INHERITED::setContext(device, paint, matrix)) {
+bool SkTwoPointRadialGradient::setContext( const SkBitmap& device,
+ const SkPaint& paint,
+ const SkMatrix& matrix){
+ // For now, we might have divided by zero, so detect that
+ if (0 == fDiffRadius) {
return false;
}
- // For now, we might have divided by zero, so detect that
- if (0 == fDiffRadius) {
+ if (!this->INHERITED::setContext(device, paint, matrix)) {
return false;
}
@@ -349,31 +348,30 @@ void SkTwoPointRadialGradient::init() {
#if SK_SUPPORT_GPU
+#include "GrTBackendEffectFactory.h"
+
// For brevity
typedef GrGLUniformManager::UniformHandle UniformHandle;
static const UniformHandle kInvalidUniformHandle = GrGLUniformManager::kInvalidUniformHandle;
-class GrGLRadial2Gradient : public GrGLGradientStage {
+class GrGLRadial2Gradient : public GrGLGradientEffect {
public:
- GrGLRadial2Gradient(const GrProgramStageFactory& factory,
- const GrCustomStage&);
+ GrGLRadial2Gradient(const GrBackendEffectFactory& factory,
+ const GrEffect&);
virtual ~GrGLRadial2Gradient() { }
- virtual void setupVariables(GrGLShaderBuilder* builder) SK_OVERRIDE;
- virtual void emitVS(GrGLShaderBuilder* builder,
- const char* vertexCoords) SK_OVERRIDE;
- virtual void emitFS(GrGLShaderBuilder* builder,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray&) SK_OVERRIDE;
- virtual void setData(const GrGLUniformManager&,
- const GrCustomStage&,
- const GrRenderTarget*,
- int stageNum) SK_OVERRIDE;
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrEffectStage&,
+ EffectKey,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+ virtual void setData(const GrGLUniformManager&, const GrEffectStage&) SK_OVERRIDE;
- static StageKey GenKey(const GrCustomStage& s, const GrGLCaps& caps);
+ static EffectKey GenKey(const GrEffectStage&, const GrGLCaps& caps);
protected:
@@ -388,15 +386,15 @@ protected:
// @{
/// Values last uploaded as uniforms
- GrScalar fCachedCenter;
- GrScalar fCachedRadius;
+ SkScalar fCachedCenter;
+ SkScalar fCachedRadius;
bool fCachedPosRoot;
// @}
private:
- typedef GrGLGradientStage INHERITED;
+ typedef GrGLGradientEffect INHERITED;
};
@@ -405,18 +403,21 @@ private:
class GrRadial2Gradient : public GrGradientEffect {
public:
- GrRadial2Gradient(GrContext* ctx, const SkTwoPointRadialGradient& shader, SkShader::TileMode tm)
- : INHERITED(ctx, shader, tm)
+ GrRadial2Gradient(GrContext* ctx,
+ const SkTwoPointRadialGradient& shader,
+ const SkMatrix& matrix,
+ SkShader::TileMode tm)
+ : INHERITED(ctx, shader, matrix, tm)
, fCenterX1(shader.getCenterX1())
, fRadius0(shader.getStartRadius())
, fPosRoot(shader.getDiffRadius() < 0) { }
virtual ~GrRadial2Gradient() { }
static const char* Name() { return "Two-Point Radial Gradient"; }
- virtual const GrProgramStageFactory& getFactory() const SK_OVERRIDE {
- return GrTProgramStageFactory<GrRadial2Gradient>::getInstance();
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<GrRadial2Gradient>::getInstance();
}
- virtual bool isEqual(const GrCustomStage& sBase) const SK_OVERRIDE {
+ virtual bool isEqual(const GrEffect& sBase) const SK_OVERRIDE {
const GrRadial2Gradient& s = static_cast<const GrRadial2Gradient&>(sBase);
return (INHERITED::isEqual(sBase) &&
this->fCenterX1 == s.fCenterX1 &&
@@ -425,22 +426,22 @@ public:
}
// The radial gradient parameters can collapse to a linear (instead of quadratic) equation.
- bool isDegenerate() const { return GR_Scalar1 == fCenterX1; }
- GrScalar center() const { return fCenterX1; }
- GrScalar radius() const { return fRadius0; }
+ bool isDegenerate() const { return SK_Scalar1 == fCenterX1; }
+ SkScalar center() const { return fCenterX1; }
+ SkScalar radius() const { return fRadius0; }
bool isPosRoot() const { return SkToBool(fPosRoot); }
- typedef GrGLRadial2Gradient GLProgramStage;
+ typedef GrGLRadial2Gradient GLEffect;
private:
- GR_DECLARE_CUSTOM_STAGE_TEST;
+ GR_DECLARE_EFFECT_TEST;
// @{
// Cache of values - these can change arbitrarily, EXCEPT
// we shouldn't change between degenerate and non-degenerate?!
- GrScalar fCenterX1;
- GrScalar fRadius0;
+ SkScalar fCenterX1;
+ SkScalar fRadius0;
SkBool8 fPosRoot;
// @}
@@ -450,17 +451,17 @@ private:
/////////////////////////////////////////////////////////////////////
-GR_DEFINE_CUSTOM_STAGE_TEST(GrRadial2Gradient);
+GR_DEFINE_EFFECT_TEST(GrRadial2Gradient);
-GrCustomStage* GrRadial2Gradient::TestCreate(SkRandom* random,
- GrContext* context,
- GrTexture**) {
+GrEffect* GrRadial2Gradient::TestCreate(SkRandom* random,
+ GrContext* context,
+ GrTexture**) {
SkPoint center1 = {random->nextUScalar1(), random->nextUScalar1()};
SkScalar radius1 = random->nextUScalar1();
SkPoint center2;
SkScalar radius2;
do {
- center1.set(random->nextUScalar1(), random->nextUScalar1());
+ center2.set(random->nextUScalar1(), random->nextUScalar1());
radius2 = random->nextUScalar1 ();
// There is a bug in two point radial gradients with idenitical radii
} while (radius1 == radius2);
@@ -474,26 +475,26 @@ GrCustomStage* GrRadial2Gradient::TestCreate(SkRandom* random,
center2, radius2,
colors, stops, colorCount,
tm));
- GrSamplerState sampler;
- shader->asNewCustomStage(context, &sampler);
- GrAssert(NULL != sampler.getCustomStage());
- // const_cast and ref is a hack! Will remove when asNewCustomStage returns GrCustomStage*
- sampler.getCustomStage()->ref();
- return const_cast<GrCustomStage*>(sampler.getCustomStage());
+ GrEffectStage stage;
+ shader->asNewEffect(context, &stage);
+ GrAssert(NULL != stage.getEffect());
+ // const_cast and ref is a hack! Will remove when asNewEffect returns GrEffect*
+ stage.getEffect()->ref();
+ return const_cast<GrEffect*>(stage.getEffect());
}
/////////////////////////////////////////////////////////////////////
GrGLRadial2Gradient::GrGLRadial2Gradient(
- const GrProgramStageFactory& factory,
- const GrCustomStage& baseData)
+ const GrBackendEffectFactory& factory,
+ const GrEffect& baseData)
: INHERITED(factory)
, fVSParamUni(kInvalidUniformHandle)
, fFSParamUni(kInvalidUniformHandle)
, fVSVaryingName(NULL)
, fFSVaryingName(NULL)
- , fCachedCenter(GR_ScalarMax)
- , fCachedRadius(-GR_ScalarMax)
+ , fCachedCenter(SK_ScalarMax)
+ , fCachedRadius(-SK_ScalarMax)
, fCachedPosRoot(0) {
const GrRadial2Gradient& data =
@@ -501,8 +502,20 @@ GrGLRadial2Gradient::GrGLRadial2Gradient(
fIsDegenerate = data.isDegenerate();
}
-void GrGLRadial2Gradient::setupVariables(GrGLShaderBuilder* builder) {
- INHERITED::setupVariables(builder);
+void GrGLRadial2Gradient::emitCode(GrGLShaderBuilder* builder,
+ const GrEffectStage& stage,
+ EffectKey key,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+
+ this->emitYCoordUniform(builder);
+ const char* fsCoords;
+ const char* vsCoordsVarying;
+ GrSLType coordsVaryingType;
+ this->setupMatrix(builder, key, vertexCoords, &fsCoords, &vsCoordsVarying, &coordsVaryingType);
+
// 2 copies of uniform array, 1 for each of vertex & fragment shader,
// to work around Xoom bug. Doesn't seem to cause performance decrease
// in test apps, but need to keep an eye on it.
@@ -513,113 +526,104 @@ void GrGLRadial2Gradient::setupVariables(GrGLShaderBuilder* builder) {
// For radial gradients without perspective we can pass the linear
// part of the quadratic as a varying.
- if (!builder->defaultTextureMatrixIsPerspective()) {
- builder->addVarying(kFloat_GrSLType, "Radial2BCoeff",
- &fVSVaryingName, &fFSVaryingName);
+ if (kVec2f_GrSLType == coordsVaryingType) {
+ builder->addVarying(kFloat_GrSLType, "Radial2BCoeff", &fVSVaryingName, &fFSVaryingName);
}
-}
-void GrGLRadial2Gradient::emitVS(GrGLShaderBuilder* builder,
- const char* vertexCoords) {
- SkString* code = &builder->fVSCode;
- SkString p2;
- SkString p3;
- builder->getUniformVariable(fVSParamUni).appendArrayAccess(2, &p2);
- builder->getUniformVariable(fVSParamUni).appendArrayAccess(3, &p3);
-
- // For radial gradients without perspective we can pass the linear
- // part of the quadratic as a varying.
- if (!builder->defaultTextureMatrixIsPerspective()) {
- // r2Var = 2 * (r2Parm[2] * varCoord.x - r2Param[3])
- code->appendf("\t%s = 2.0 *(%s * %s.x - %s);\n",
- fVSVaryingName, p2.c_str(),
- vertexCoords, p3.c_str());
+ // VS
+ {
+ SkString* code = &builder->fVSCode;
+ SkString p2;
+ SkString p3;
+ builder->getUniformVariable(fVSParamUni).appendArrayAccess(2, &p2);
+ builder->getUniformVariable(fVSParamUni).appendArrayAccess(3, &p3);
+
+ // For radial gradients without perspective we can pass the linear
+ // part of the quadratic as a varying.
+ if (kVec2f_GrSLType == coordsVaryingType) {
+ // r2Var = 2 * (r2Parm[2] * varCoord.x - r2Param[3])
+ code->appendf("\t%s = 2.0 *(%s * %s.x - %s);\n",
+ fVSVaryingName, p2.c_str(),
+ vsCoordsVarying, p3.c_str());
+ }
}
-}
-void GrGLRadial2Gradient::emitFS(GrGLShaderBuilder* builder,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray& samplers) {
- SkString* code = &builder->fFSCode;
- SkString cName("c");
- SkString ac4Name("ac4");
- SkString rootName("root");
- SkString t;
- SkString p0;
- SkString p1;
- SkString p2;
- SkString p3;
- SkString p4;
- SkString p5;
- builder->getUniformVariable(fFSParamUni).appendArrayAccess(0, &p0);
- builder->getUniformVariable(fFSParamUni).appendArrayAccess(1, &p1);
- builder->getUniformVariable(fFSParamUni).appendArrayAccess(2, &p2);
- builder->getUniformVariable(fFSParamUni).appendArrayAccess(3, &p3);
- builder->getUniformVariable(fFSParamUni).appendArrayAccess(4, &p4);
- builder->getUniformVariable(fFSParamUni).appendArrayAccess(5, &p5);
-
- // If we we're able to interpolate the linear component,
- // bVar is the varying; otherwise compute it
- SkString bVar;
- if (!builder->defaultTextureMatrixIsPerspective()) {
- bVar = fFSVaryingName;
- } else {
- bVar = "b";
- //bVar.appendS32(stageNum);
- code->appendf("\tfloat %s = 2.0 * (%s * %s.x - %s);\n",
- bVar.c_str(), p2.c_str(),
- builder->defaultTexCoordsName(), p3.c_str());
- }
+ // FS
+ {
+ SkString* code = &builder->fFSCode;
+ SkString cName("c");
+ SkString ac4Name("ac4");
+ SkString rootName("root");
+ SkString t;
+ SkString p0;
+ SkString p1;
+ SkString p2;
+ SkString p3;
+ SkString p4;
+ SkString p5;
+ builder->getUniformVariable(fFSParamUni).appendArrayAccess(0, &p0);
+ builder->getUniformVariable(fFSParamUni).appendArrayAccess(1, &p1);
+ builder->getUniformVariable(fFSParamUni).appendArrayAccess(2, &p2);
+ builder->getUniformVariable(fFSParamUni).appendArrayAccess(3, &p3);
+ builder->getUniformVariable(fFSParamUni).appendArrayAccess(4, &p4);
+ builder->getUniformVariable(fFSParamUni).appendArrayAccess(5, &p5);
+
+ // If we we're able to interpolate the linear component,
+ // bVar is the varying; otherwise compute it
+ SkString bVar;
+ if (kVec2f_GrSLType == coordsVaryingType) {
+ bVar = fFSVaryingName;
+ } else {
+ bVar = "b";
+ code->appendf("\tfloat %s = 2.0 * (%s * %s.x - %s);\n",
+ bVar.c_str(), p2.c_str(), fsCoords, p3.c_str());
+ }
- // c = (x^2)+(y^2) - params[4]
- code->appendf("\tfloat %s = dot(%s, %s) - %s;\n",
- cName.c_str(),
- builder->defaultTexCoordsName(),
- builder->defaultTexCoordsName(),
- p4.c_str());
-
- // If we aren't degenerate, emit some extra code, and accept a slightly
- // more complex coord.
- if (!fIsDegenerate) {
-
- // ac4 = 4.0 * params[0] * c
- code->appendf("\tfloat %s = %s * 4.0 * %s;\n",
- ac4Name.c_str(), p0.c_str(),
- cName.c_str());
-
- // root = sqrt(b^2-4ac)
- // (abs to avoid exception due to fp precision)
- code->appendf("\tfloat %s = sqrt(abs(%s*%s - %s));\n",
- rootName.c_str(), bVar.c_str(), bVar.c_str(),
- ac4Name.c_str());
-
- // t is: (-b + params[5] * sqrt(b^2-4ac)) * params[1]
- t.printf("(-%s + %s * %s) * %s", bVar.c_str(), p5.c_str(),
- rootName.c_str(), p1.c_str());
- } else {
- // t is: -c/b
- t.printf("-%s / %s", cName.c_str(), bVar.c_str());
- }
+ // c = (x^2)+(y^2) - params[4]
+ code->appendf("\tfloat %s = dot(%s, %s) - %s;\n",
+ cName.c_str(),
+ fsCoords,
+ fsCoords,
+ p4.c_str());
+
+ // If we aren't degenerate, emit some extra code, and accept a slightly
+ // more complex coord.
+ if (!fIsDegenerate) {
+
+ // ac4 = 4.0 * params[0] * c
+ code->appendf("\tfloat %s = %s * 4.0 * %s;\n",
+ ac4Name.c_str(), p0.c_str(),
+ cName.c_str());
+
+ // root = sqrt(b^2-4ac)
+ // (abs to avoid exception due to fp precision)
+ code->appendf("\tfloat %s = sqrt(abs(%s*%s - %s));\n",
+ rootName.c_str(), bVar.c_str(), bVar.c_str(),
+ ac4Name.c_str());
+
+ // t is: (-b + params[5] * sqrt(b^2-4ac)) * params[1]
+ t.printf("(-%s + %s * %s) * %s", bVar.c_str(), p5.c_str(),
+ rootName.c_str(), p1.c_str());
+ } else {
+ // t is: -c/b
+ t.printf("-%s / %s", cName.c_str(), bVar.c_str());
+ }
- this->emitColorLookup(builder, t.c_str(), outputColor, inputColor, samplers[0]);
+ this->emitColorLookup(builder, t.c_str(), outputColor, inputColor, samplers[0]);
+ }
}
-void GrGLRadial2Gradient::setData(const GrGLUniformManager& uman,
- const GrCustomStage& baseData,
- const GrRenderTarget* target,
- int stageNum) {
- INHERITED::setData(uman, baseData, target, stageNum);
- const GrRadial2Gradient& data =
- static_cast<const GrRadial2Gradient&>(baseData);
+void GrGLRadial2Gradient::setData(const GrGLUniformManager& uman, const GrEffectStage& stage) {
+ INHERITED::setData(uman, stage);
+ const GrRadial2Gradient& data = static_cast<const GrRadial2Gradient&>(*stage.getEffect());
GrAssert(data.isDegenerate() == fIsDegenerate);
- GrScalar centerX1 = data.center();
- GrScalar radius0 = data.radius();
+ SkScalar centerX1 = data.center();
+ SkScalar radius0 = data.radius();
if (fCachedCenter != centerX1 ||
fCachedRadius != radius0 ||
fCachedPosRoot != data.isPosRoot()) {
- GrScalar a = GrMul(centerX1, centerX1) - GR_Scalar1;
+ SkScalar a = SkScalarMul(centerX1, centerX1) - SK_Scalar1;
// When we're in the degenerate (linear) case, the second
// value will be INF but the program doesn't read it. (We
@@ -627,11 +631,11 @@ void GrGLRadial2Gradient::setData(const GrGLUniformManager& uman,
// all in the linear case just to keep the code complexity
// down).
float values[6] = {
- GrScalarToFloat(a),
- 1 / (2.f * GrScalarToFloat(a)),
- GrScalarToFloat(centerX1),
- GrScalarToFloat(radius0),
- GrScalarToFloat(GrMul(radius0, radius0)),
+ SkScalarToFloat(a),
+ 1 / (2.f * SkScalarToFloat(a)),
+ SkScalarToFloat(centerX1),
+ SkScalarToFloat(radius0),
+ SkScalarToFloat(SkScalarMul(radius0, radius0)),
data.isPosRoot() ? 1.f : -1.f
};
@@ -643,42 +647,46 @@ void GrGLRadial2Gradient::setData(const GrGLUniformManager& uman,
}
}
-GrCustomStage::StageKey GrGLRadial2Gradient::GenKey(const GrCustomStage& s, const GrGLCaps& caps) {
- return (static_cast<const GrRadial2Gradient&>(s).isDegenerate());
+GrGLEffect::EffectKey GrGLRadial2Gradient::GenKey(const GrEffectStage& s, const GrGLCaps&) {
+ enum {
+ kIsDegenerate = 1 << kMatrixKeyBitCnt,
+ };
+
+ EffectKey key = GenMatrixKey(s);
+ if (static_cast<const GrRadial2Gradient&>(*s.getEffect()).isDegenerate()) {
+ key |= kIsDegenerate;
+ }
+ return key;
}
/////////////////////////////////////////////////////////////////////
-bool SkTwoPointRadialGradient::asNewCustomStage(GrContext* context,
- GrSamplerState* sampler) const {
- SkASSERT(NULL != context && NULL != sampler);
- SkScalar diffLen = fDiff.length();
+bool SkTwoPointRadialGradient::asNewEffect(GrContext* context,
+ GrEffectStage* stage) const {
+ SkASSERT(NULL != context && NULL != stage);
+ // invert the localM, translate to center1 (fPtsToUni), rotate so center2 is on x axis.
SkMatrix matrix;
- if (0 != diffLen) {
- SkScalar invDiffLen = SkScalarInvert(diffLen);
- matrix.setSinCos(-SkScalarMul(invDiffLen, fDiff.fY),
- SkScalarMul(invDiffLen, fDiff.fX));
- } else {
- matrix.reset();
+ if (!this->getLocalMatrix().invert(&matrix)) {
+ return false;
}
-
- matrix.preConcat(fPtsToUnit);
+ matrix.postConcat(fPtsToUnit);
- SkMatrix localM;
- if (this->getLocalMatrix(&localM)) {
- if (!localM.invert(&localM)) {
- return false;
- }
- matrix.preConcat(localM);
+ SkScalar diffLen = fDiff.length();
+ if (0 != diffLen) {
+ SkScalar invDiffLen = SkScalarInvert(diffLen);
+ SkMatrix rot;
+ rot.setSinCos(-SkScalarMul(invDiffLen, fDiff.fY),
+ SkScalarMul(invDiffLen, fDiff.fX));
+ matrix.postConcat(rot);
}
- sampler->setCustomStage(SkNEW_ARGS(GrRadial2Gradient, (context, *this, fTileMode)), matrix)->unref();
+ stage->setEffect(SkNEW_ARGS(GrRadial2Gradient, (context, *this, matrix, fTileMode)))->unref();
return true;
}
#else
-bool SkTwoPointRadialGradient::asNewCustomStage(GrContext*, GrSamplerState*) const {
+bool SkTwoPointRadialGradient::asNewEffect(GrContext*, GrEffectStage*) const {
SkDEBUGFAIL("Should not call in GPU-less build");
return false;
}
diff --git a/src/effects/gradients/SkTwoPointRadialGradient.h b/src/effects/gradients/SkTwoPointRadialGradient.h
index adbb602265..e7e451afe9 100644
--- a/src/effects/gradients/SkTwoPointRadialGradient.h
+++ b/src/effects/gradients/SkTwoPointRadialGradient.h
@@ -23,7 +23,7 @@ public:
SkMatrix* matrix,
TileMode* xy) const SK_OVERRIDE;
virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE;
- virtual bool asNewCustomStage(GrContext* context, GrSamplerState* sampler) const SK_OVERRIDE;
+ virtual bool asNewEffect(GrContext* context, GrEffectStage* stage) const SK_OVERRIDE;
virtual void shadeSpan(int x, int y, SkPMColor* dstCParam,
int count) SK_OVERRIDE;
diff --git a/src/gpu/GrAAConvexPathRenderer.cpp b/src/gpu/GrAAConvexPathRenderer.cpp
index a525968283..e0a80d3399 100644
--- a/src/gpu/GrAAConvexPathRenderer.cpp
+++ b/src/gpu/GrAAConvexPathRenderer.cpp
@@ -12,9 +12,9 @@
#include "GrDrawState.h"
#include "GrPathUtils.h"
#include "SkString.h"
+#include "SkStrokeRec.h"
#include "SkTrace.h"
-
GrAAConvexPathRenderer::GrAAConvexPathRenderer() {
}
@@ -52,7 +52,7 @@ struct Segment {
typedef SkTArray<Segment, true> SegmentArray;
void center_of_mass(const SegmentArray& segments, SkPoint* c) {
- GrScalar area = 0;
+ SkScalar area = 0;
SkPoint center = {0, 0};
int count = segments.count();
SkPoint p0 = {0, 0};
@@ -71,7 +71,7 @@ void center_of_mass(const SegmentArray& segments, SkPoint* c) {
pi = pj;
const SkPoint pj = segments[i + 1].endPt() - p0;
- GrScalar t = GrMul(pi.fX, pj.fY) - GrMul(pj.fX, pi.fY);
+ SkScalar t = SkScalarMul(pi.fX, pj.fY) - SkScalarMul(pj.fX, pi.fY);
area += t;
center.fX += (pi.fX + pj.fX) * t;
center.fY += (pi.fY + pj.fY) * t;
@@ -93,9 +93,9 @@ void center_of_mass(const SegmentArray& segments, SkPoint* c) {
*c = avg;
} else {
area *= 3;
- area = GrScalarDiv(GR_Scalar1, area);
- center.fX = GrScalarMul(center.fX, area);
- center.fY = GrScalarMul(center.fY, area);
+ area = SkScalarDiv(SK_Scalar1, area);
+ center.fX = SkScalarMul(center.fX, area);
+ center.fY = SkScalarMul(center.fY, area);
// undo the translate of p0 to the origin.
*c = center + p0;
}
@@ -168,7 +168,7 @@ struct DegenerateTestData {
} fStage;
GrPoint fFirstPoint;
GrVec fLineNormal;
- GrScalar fLineC;
+ SkScalar fLineC;
};
void update_degenerate_test(DegenerateTestData* data, const GrPoint& pt) {
@@ -200,30 +200,28 @@ void update_degenerate_test(DegenerateTestData* data, const GrPoint& pt) {
}
}
-inline bool get_direction(const SkPath& path, const GrMatrix& m, SkPath::Direction* dir) {
+inline bool get_direction(const SkPath& path, const SkMatrix& m, SkPath::Direction* dir) {
if (!path.cheapComputeDirection(dir)) {
return false;
}
// check whether m reverses the orientation
GrAssert(!m.hasPerspective());
- GrScalar det2x2 = GrMul(m.get(SkMatrix::kMScaleX), m.get(SkMatrix::kMScaleY)) -
- GrMul(m.get(SkMatrix::kMSkewX), m.get(SkMatrix::kMSkewY));
+ SkScalar det2x2 = SkScalarMul(m.get(SkMatrix::kMScaleX), m.get(SkMatrix::kMScaleY)) -
+ SkScalarMul(m.get(SkMatrix::kMSkewX), m.get(SkMatrix::kMSkewY));
if (det2x2 < 0) {
- GR_STATIC_ASSERT(0 == SkPath::kCW_Direction || 1 == SkPath::kCW_Direction);
- GR_STATIC_ASSERT(0 == SkPath::kCCW_Direction || 1 == SkPath::kCCW_Direction);
- *dir = static_cast<SkPath::Direction>(*dir ^ 0x1);
+ *dir = SkPath::OppositeDirection(*dir);
}
return true;
}
bool get_segments(const SkPath& path,
- const GrMatrix& m,
+ const SkMatrix& m,
SegmentArray* segments,
SkPoint* fanPt,
int* vCount,
int* iCount) {
SkPath::Iter iter(path, true);
- // This renderer overemphasises very thin path regions. We use the distance
+ // This renderer over-emphasizes very thin path regions. We use the distance
// to the path from the sample to compute coverage. Every pixel intersected
// by the path will be hit and the maximum distance is sqrt(2)/2. We don't
// notice that the sample may be close to a very thin area of the path and
@@ -296,8 +294,8 @@ bool get_segments(const SkPath& path,
struct QuadVertex {
GrPoint fPos;
GrPoint fUV;
- GrScalar fD0;
- GrScalar fD1;
+ SkScalar fD0;
+ SkScalar fD1;
};
void create_vertices(const SegmentArray& segments,
@@ -347,7 +345,7 @@ void create_vertices(const SegmentArray& segments,
// we draw the line edge as a degenerate quad (u is 0, v is the
// signed distance to the edge)
- GrScalar dist = fanPt.distanceToLineBetween(verts[v + 1].fPos,
+ SkScalar dist = fanPt.distanceToLineBetween(verts[v + 1].fPos,
verts[v + 2].fPos);
verts[v + 0].fUV.set(0, dist);
verts[v + 1].fUV.set(0, 0);
@@ -388,21 +386,21 @@ void create_vertices(const SegmentArray& segments,
verts[v + 4].fPos = qpts[2] + segb.fNorms[1];
verts[v + 5].fPos = qpts[1] + midVec;
- GrScalar c = segb.fNorms[0].dot(qpts[0]);
+ SkScalar c = segb.fNorms[0].dot(qpts[0]);
verts[v + 0].fD0 = -segb.fNorms[0].dot(fanPt) + c;
verts[v + 1].fD0 = 0.f;
verts[v + 2].fD0 = -segb.fNorms[0].dot(qpts[2]) + c;
- verts[v + 3].fD0 = -GR_ScalarMax/100;
- verts[v + 4].fD0 = -GR_ScalarMax/100;
- verts[v + 5].fD0 = -GR_ScalarMax/100;
+ verts[v + 3].fD0 = -SK_ScalarMax/100;
+ verts[v + 4].fD0 = -SK_ScalarMax/100;
+ verts[v + 5].fD0 = -SK_ScalarMax/100;
c = segb.fNorms[1].dot(qpts[2]);
verts[v + 0].fD1 = -segb.fNorms[1].dot(fanPt) + c;
verts[v + 1].fD1 = -segb.fNorms[1].dot(qpts[0]) + c;
verts[v + 2].fD1 = 0.f;
- verts[v + 3].fD1 = -GR_ScalarMax/100;
- verts[v + 4].fD1 = -GR_ScalarMax/100;
- verts[v + 5].fD1 = -GR_ScalarMax/100;
+ verts[v + 3].fD1 = -SK_ScalarMax/100;
+ verts[v + 4].fD1 = -SK_ScalarMax/100;
+ verts[v + 5].fD1 = -SK_ScalarMax/100;
GrPathUtils::QuadUVMatrix toUV(qpts);
toUV.apply<6, sizeof(QuadVertex), sizeof(GrPoint)>(verts + v);
@@ -431,20 +429,15 @@ void create_vertices(const SegmentArray& segments,
}
bool GrAAConvexPathRenderer::canDrawPath(const SkPath& path,
- GrPathFill fill,
+ const SkStrokeRec& stroke,
const GrDrawTarget* target,
bool antiAlias) const {
- if (!target->getCaps().shaderDerivativeSupport() || !antiAlias ||
- kHairLine_GrPathFill == fill || GrIsFillInverted(fill) ||
- !path.isConvex()) {
- return false;
- } else {
- return true;
- }
+ return (target->getCaps().shaderDerivativeSupport() && antiAlias &&
+ stroke.isFillStyle() && !path.isInverseFillType() && path.isConvex());
}
bool GrAAConvexPathRenderer::onDrawPath(const SkPath& origPath,
- GrPathFill fill,
+ const SkStrokeRec&,
GrDrawTarget* target,
bool antiAlias) {
@@ -458,7 +451,7 @@ bool GrAAConvexPathRenderer::onDrawPath(const SkPath& origPath,
if (!adcd.succeeded()) {
return false;
}
- const GrMatrix* vm = &adcd.getOriginalMatrix();
+ const SkMatrix* vm = &adcd.getOriginalMatrix();
GrVertexLayout layout = 0;
layout |= GrDrawTarget::kEdge_VertexLayoutBit;
@@ -470,7 +463,7 @@ bool GrAAConvexPathRenderer::onDrawPath(const SkPath& origPath,
if (vm->hasPerspective()) {
origPath.transform(*vm, &tmpPath);
path = &tmpPath;
- vm = &GrMatrix::I();
+ vm = &SkMatrix::I();
}
QuadVertex *verts;
diff --git a/src/gpu/GrAAConvexPathRenderer.h b/src/gpu/GrAAConvexPathRenderer.h
index 7a2fd38b06..62394a3a74 100644
--- a/src/gpu/GrAAConvexPathRenderer.h
+++ b/src/gpu/GrAAConvexPathRenderer.h
@@ -14,13 +14,13 @@ public:
GrAAConvexPathRenderer();
virtual bool canDrawPath(const SkPath& path,
- GrPathFill fill,
+ const SkStrokeRec& stroke,
const GrDrawTarget* target,
bool antiAlias) const SK_OVERRIDE;
protected:
virtual bool onDrawPath(const SkPath& path,
- GrPathFill fill,
+ const SkStrokeRec& stroke,
GrDrawTarget* target,
bool antiAlias) SK_OVERRIDE;
};
diff --git a/src/gpu/GrAAHairLinePathRenderer.cpp b/src/gpu/GrAAHairLinePathRenderer.cpp
index 06d8e71f98..347e4b5bee 100644
--- a/src/gpu/GrAAHairLinePathRenderer.cpp
+++ b/src/gpu/GrAAHairLinePathRenderer.cpp
@@ -14,6 +14,7 @@
#include "GrIndexBuffer.h"
#include "GrPathUtils.h"
#include "SkGeometry.h"
+#include "SkStroke.h"
#include "SkTemplates.h"
namespace {
@@ -147,7 +148,7 @@ int num_quad_subdivs(const SkPoint p[3]) {
return -1;
}
- GrScalar dsqd = p[1].distanceToLineBetweenSqd(p[0], p[2]);
+ SkScalar dsqd = p[1].distanceToLineBetweenSqd(p[0], p[2]);
if (dsqd < gDegenerateToLineTolSqd) {
return -1;
}
@@ -322,13 +323,13 @@ struct Vertex {
GrPoint fPos;
union {
struct {
- GrScalar fA;
- GrScalar fB;
- GrScalar fC;
+ SkScalar fA;
+ SkScalar fB;
+ SkScalar fC;
} fLine;
GrVec fQuadCoord;
struct {
- GrScalar fBogus[4];
+ SkScalar fBogus[4];
};
};
};
@@ -352,8 +353,8 @@ void intersect_lines(const SkPoint& ptA, const SkVector& normA,
result->fY = SkScalarMul(result->fY, wInv);
}
-void bloat_quad(const SkPoint qpts[3], const GrMatrix* toDevice,
- const GrMatrix* toSrc, Vertex verts[kVertsPerQuad]) {
+void bloat_quad(const SkPoint qpts[3], const SkMatrix* toDevice,
+ const SkMatrix* toSrc, Vertex verts[kVertsPerQuad]) {
GrAssert(!toDevice == !toSrc);
// original quad is specified by tri a,b,c
SkPoint a = qpts[0];
@@ -430,8 +431,8 @@ void bloat_quad(const SkPoint qpts[3], const GrMatrix* toDevice,
void add_quads(const SkPoint p[3],
int subdiv,
- const GrMatrix* toDevice,
- const GrMatrix* toSrc,
+ const SkMatrix* toDevice,
+ const SkMatrix* toSrc,
Vertex** vert) {
GrAssert(subdiv >= 0);
if (subdiv) {
@@ -458,16 +459,7 @@ void add_line(const SkPoint p[2],
if (orthVec.setLength(SK_Scalar1)) {
orthVec.setOrthog(orthVec);
- // the values we pass down to the frag shader
- // have to be in y-points-up space;
- SkVector normal;
- normal.fX = orthVec.fX;
- normal.fY = -orthVec.fY;
- SkPoint aYDown;
- aYDown.fX = a.fX;
- aYDown.fY = rtHeight - a.fY;
-
- SkScalar lineC = -(aYDown.dot(normal));
+ SkScalar lineC = -(a.dot(orthVec));
for (int i = 0; i < kVertsPerLineSeg; ++i) {
(*vert)[i].fPos = (i < 2) ? a : b;
if (0 == i || 3 == i) {
@@ -475,8 +467,8 @@ void add_line(const SkPoint p[2],
} else {
(*vert)[i].fPos += orthVec;
}
- (*vert)[i].fLine.fA = normal.fX;
- (*vert)[i].fLine.fB = normal.fY;
+ (*vert)[i].fLine.fA = orthVec.fX;
+ (*vert)[i].fLine.fB = orthVec.fY;
(*vert)[i].fLine.fC = lineC;
}
if (NULL != toSrc) {
@@ -511,7 +503,7 @@ bool GrAAHairLinePathRenderer::createGeom(
&devClipBounds);
GrVertexLayout layout = GrDrawTarget::kEdge_VertexLayoutBit;
- GrMatrix viewM = drawState.getViewMatrix();
+ SkMatrix viewM = drawState.getViewMatrix();
PREALLOC_PTARRAY(128) lines;
PREALLOC_PTARRAY(128) quads;
@@ -530,9 +522,9 @@ bool GrAAHairLinePathRenderer::createGeom(
Vertex* verts = reinterpret_cast<Vertex*>(arg->vertices());
- const GrMatrix* toDevice = NULL;
- const GrMatrix* toSrc = NULL;
- GrMatrix ivm;
+ const SkMatrix* toDevice = NULL;
+ const SkMatrix* toSrc = NULL;
+ SkMatrix ivm;
if (viewM.hasPerspective()) {
if (viewM.invert(&ivm)) {
@@ -555,10 +547,10 @@ bool GrAAHairLinePathRenderer::createGeom(
}
bool GrAAHairLinePathRenderer::canDrawPath(const SkPath& path,
- GrPathFill fill,
+ const SkStrokeRec& stroke,
const GrDrawTarget* target,
bool antiAlias) const {
- if (fill != kHairLine_GrPathFill || !antiAlias) {
+ if (!stroke.isHairlineStyle() || !antiAlias) {
return false;
}
@@ -572,7 +564,7 @@ bool GrAAHairLinePathRenderer::canDrawPath(const SkPath& path,
}
bool GrAAHairLinePathRenderer::onDrawPath(const SkPath& path,
- GrPathFill fill,
+ const SkStrokeRec&,
GrDrawTarget* target,
bool antiAlias) {
diff --git a/src/gpu/GrAAHairLinePathRenderer.h b/src/gpu/GrAAHairLinePathRenderer.h
index 9129a89674..20be696745 100644
--- a/src/gpu/GrAAHairLinePathRenderer.h
+++ b/src/gpu/GrAAHairLinePathRenderer.h
@@ -18,13 +18,13 @@ public:
static GrPathRenderer* Create(GrContext* context);
virtual bool canDrawPath(const SkPath& path,
- GrPathFill fill,
- const GrDrawTarget* target,
- bool antiAlias) const SK_OVERRIDE;
+ const SkStrokeRec& stroke,
+ const GrDrawTarget* target,
+ bool antiAlias) const SK_OVERRIDE;
protected:
virtual bool onDrawPath(const SkPath& path,
- GrPathFill fill,
+ const SkStrokeRec& stroke,
GrDrawTarget* target,
bool antiAlias) SK_OVERRIDE;
diff --git a/src/gpu/GrAARectRenderer.cpp b/src/gpu/GrAARectRenderer.cpp
index 36f37abf40..b9d17f8802 100644
--- a/src/gpu/GrAARectRenderer.cpp
+++ b/src/gpu/GrAARectRenderer.cpp
@@ -13,8 +13,7 @@ SK_DEFINE_INST_COUNT(GrAARectRenderer)
namespace {
-static GrVertexLayout aa_rect_layout(const GrDrawTarget* target,
- bool useCoverage) {
+static GrVertexLayout aa_rect_layout(bool useCoverage) {
GrVertexLayout layout = 0;
if (useCoverage) {
layout |= GrDrawTarget::kCoverage_VertexLayoutBit;
@@ -24,8 +23,8 @@ static GrVertexLayout aa_rect_layout(const GrDrawTarget* target,
return layout;
}
-static void setInsetFan(GrPoint* pts, size_t stride,
- const GrRect& r, GrScalar dx, GrScalar dy) {
+static void set_inset_fan(GrPoint* pts, size_t stride,
+ const GrRect& r, SkScalar dx, SkScalar dy) {
pts->setRectFan(r.fLeft + dx, r.fTop + dy,
r.fRight - dx, r.fBottom - dy, stride);
}
@@ -37,7 +36,7 @@ void GrAARectRenderer::reset() {
GrSafeSetNull(fAAStrokeRectIndexBuffer);
}
-const uint16_t GrAARectRenderer::gFillAARectIdx[] = {
+static const uint16_t gFillAARectIdx[] = {
0, 1, 5, 5, 4, 0,
1, 2, 6, 6, 5, 1,
2, 3, 7, 7, 6, 2,
@@ -45,27 +44,47 @@ const uint16_t GrAARectRenderer::gFillAARectIdx[] = {
4, 5, 6, 6, 7, 4,
};
-int GrAARectRenderer::aaFillRectIndexCount() {
- return GR_ARRAY_COUNT(gFillAARectIdx);
-}
+static const int kIndicesPerAAFillRect = GR_ARRAY_COUNT(gFillAARectIdx);
+static const int kVertsPerAAFillRect = 8;
+static const int kNumAAFillRectsInIndexBuffer = 256;
GrIndexBuffer* GrAARectRenderer::aaFillRectIndexBuffer(GrGpu* gpu) {
+ static const size_t kAAFillRectIndexBufferSize = kIndicesPerAAFillRect *
+ sizeof(uint16_t) *
+ kNumAAFillRectsInIndexBuffer;
+
if (NULL == fAAFillRectIndexBuffer) {
- fAAFillRectIndexBuffer = gpu->createIndexBuffer(sizeof(gFillAARectIdx),
- false);
+ fAAFillRectIndexBuffer = gpu->createIndexBuffer(kAAFillRectIndexBufferSize, false);
if (NULL != fAAFillRectIndexBuffer) {
-#if GR_DEBUG
- bool updated =
-#endif
- fAAFillRectIndexBuffer->updateData(gFillAARectIdx,
- sizeof(gFillAARectIdx));
- GR_DEBUGASSERT(updated);
+ uint16_t* data = (uint16_t*) fAAFillRectIndexBuffer->lock();
+ bool useTempData = (NULL == data);
+ if (useTempData) {
+ data = SkNEW_ARRAY(uint16_t, kNumAAFillRectsInIndexBuffer * kIndicesPerAAFillRect);
+ }
+ for (int i = 0; i < kNumAAFillRectsInIndexBuffer; ++i) {
+ // Each AA filled rect is drawn with 8 vertices and 10 triangles (8 around
+ // the inner rect (for AA) and 2 for the inner rect.
+ int baseIdx = i * kIndicesPerAAFillRect;
+ uint16_t baseVert = (uint16_t)(i * kVertsPerAAFillRect);
+ for (int j = 0; j < kIndicesPerAAFillRect; ++j) {
+ data[baseIdx+j] = baseVert + gFillAARectIdx[j];
+ }
+ }
+ if (useTempData) {
+ if (!fAAFillRectIndexBuffer->updateData(data, kAAFillRectIndexBufferSize)) {
+ GrCrash("Can't get AA Fill Rect indices into buffer!");
+ }
+ SkDELETE_ARRAY(data);
+ } else {
+ fAAFillRectIndexBuffer->unlock();
+ }
}
}
+
return fAAFillRectIndexBuffer;
}
-const uint16_t GrAARectRenderer::gStrokeAARectIdx[] = {
+static const uint16_t gStrokeAARectIdx[] = {
0 + 0, 1 + 0, 5 + 0, 5 + 0, 4 + 0, 0 + 0,
1 + 0, 2 + 0, 6 + 0, 6 + 0, 5 + 0, 1 + 0,
2 + 0, 3 + 0, 7 + 0, 7 + 0, 6 + 0, 2 + 0,
@@ -106,7 +125,7 @@ void GrAARectRenderer::fillAARect(GrGpu* gpu,
GrDrawTarget* target,
const GrRect& devRect,
bool useVertexCoverage) {
- GrVertexLayout layout = aa_rect_layout(target, useVertexCoverage);
+ GrVertexLayout layout = aa_rect_layout(useVertexCoverage);
size_t vsize = GrDrawTarget::VertexSize(layout);
@@ -115,6 +134,7 @@ void GrAARectRenderer::fillAARect(GrGpu* gpu,
GrPrintf("Failed to get space for vertices!\n");
return;
}
+
GrIndexBuffer* indexBuffer = this->aaFillRectIndexBuffer(gpu);
if (NULL == indexBuffer) {
GrPrintf("Failed to create index buffer!\n");
@@ -126,8 +146,8 @@ void GrAARectRenderer::fillAARect(GrGpu* gpu,
GrPoint* fan0Pos = reinterpret_cast<GrPoint*>(verts);
GrPoint* fan1Pos = reinterpret_cast<GrPoint*>(verts + 4 * vsize);
- setInsetFan(fan0Pos, vsize, devRect, -GR_ScalarHalf, -GR_ScalarHalf);
- setInsetFan(fan1Pos, vsize, devRect, GR_ScalarHalf, GR_ScalarHalf);
+ set_inset_fan(fan0Pos, vsize, devRect, -SK_ScalarHalf, -SK_ScalarHalf);
+ set_inset_fan(fan1Pos, vsize, devRect, SK_ScalarHalf, SK_ScalarHalf);
verts += sizeof(GrPoint);
for (int i = 0; i < 4; ++i) {
@@ -147,9 +167,9 @@ void GrAARectRenderer::fillAARect(GrGpu* gpu,
}
target->setIndexSourceToBuffer(indexBuffer);
-
- target->drawIndexed(kTriangles_GrPrimitiveType, 0,
- 0, 8, this->aaFillRectIndexCount());
+ target->drawIndexedInstances(kTriangles_GrPrimitiveType, 1,
+ kVertsPerAAFillRect,
+ kIndicesPerAAFillRect);
}
void GrAARectRenderer::strokeAARect(GrGpu* gpu,
@@ -157,15 +177,15 @@ void GrAARectRenderer::strokeAARect(GrGpu* gpu,
const GrRect& devRect,
const GrVec& devStrokeSize,
bool useVertexCoverage) {
- const GrScalar& dx = devStrokeSize.fX;
- const GrScalar& dy = devStrokeSize.fY;
- const GrScalar rx = GrMul(dx, GR_ScalarHalf);
- const GrScalar ry = GrMul(dy, GR_ScalarHalf);
+ const SkScalar& dx = devStrokeSize.fX;
+ const SkScalar& dy = devStrokeSize.fY;
+ const SkScalar rx = SkScalarMul(dx, SK_ScalarHalf);
+ const SkScalar ry = SkScalarMul(dy, SK_ScalarHalf);
- GrScalar spare;
+ SkScalar spare;
{
- GrScalar w = devRect.width() - dx;
- GrScalar h = devRect.height() - dy;
+ SkScalar w = devRect.width() - dx;
+ SkScalar h = devRect.height() - dy;
spare = GrMin(w, h);
}
@@ -175,7 +195,7 @@ void GrAARectRenderer::strokeAARect(GrGpu* gpu,
this->fillAARect(gpu, target, r, useVertexCoverage);
return;
}
- GrVertexLayout layout = aa_rect_layout(target, useVertexCoverage);
+ GrVertexLayout layout = aa_rect_layout(useVertexCoverage);
size_t vsize = GrDrawTarget::VertexSize(layout);
GrDrawTarget::AutoReleaseGeometry geo(target, layout, 16, 0);
@@ -199,14 +219,14 @@ void GrAARectRenderer::strokeAARect(GrGpu* gpu,
GrPoint* fan2Pos = reinterpret_cast<GrPoint*>(verts + 8 * vsize);
GrPoint* fan3Pos = reinterpret_cast<GrPoint*>(verts + 12 * vsize);
- setInsetFan(fan0Pos, vsize, devRect,
- -rx - GR_ScalarHalf, -ry - GR_ScalarHalf);
- setInsetFan(fan1Pos, vsize, devRect,
- -rx + GR_ScalarHalf, -ry + GR_ScalarHalf);
- setInsetFan(fan2Pos, vsize, devRect,
- rx - GR_ScalarHalf, ry - GR_ScalarHalf);
- setInsetFan(fan3Pos, vsize, devRect,
- rx + GR_ScalarHalf, ry + GR_ScalarHalf);
+ set_inset_fan(fan0Pos, vsize, devRect,
+ -rx - SK_ScalarHalf, -ry - SK_ScalarHalf);
+ set_inset_fan(fan1Pos, vsize, devRect,
+ -rx + SK_ScalarHalf, -ry + SK_ScalarHalf);
+ set_inset_fan(fan2Pos, vsize, devRect,
+ rx - SK_ScalarHalf, ry - SK_ScalarHalf);
+ set_inset_fan(fan3Pos, vsize, devRect,
+ rx + SK_ScalarHalf, ry + SK_ScalarHalf);
// The outermost rect has 0 coverage
verts += sizeof(GrPoint);
diff --git a/src/gpu/GrAddPathRenderers_default.cpp b/src/gpu/GrAddPathRenderers_default.cpp
index 24f10171e7..69be15a271 100644
--- a/src/gpu/GrAddPathRenderers_default.cpp
+++ b/src/gpu/GrAddPathRenderers_default.cpp
@@ -10,19 +10,13 @@
#include "GrStencilAndCoverPathRenderer.h"
#include "GrAAHairLinePathRenderer.h"
#include "GrAAConvexPathRenderer.h"
-#include "GrSoftwarePathRenderer.h"
-void GrPathRenderer::AddPathRenderers(GrContext* ctx,
- GrPathRendererChain::UsageFlags flags,
- GrPathRendererChain* chain) {
+void GrPathRenderer::AddPathRenderers(GrContext* ctx, GrPathRendererChain* chain) {
if (GrPathRenderer* pr = GrStencilAndCoverPathRenderer::Create(ctx)) {
chain->addPathRenderer(pr)->unref();
}
- if (!(GrPathRendererChain::kNonAAOnly_UsageFlag & flags)) {
-
- if (GrPathRenderer* pr = GrAAHairLinePathRenderer::Create(ctx)) {
- chain->addPathRenderer(pr)->unref();
- }
- chain->addPathRenderer(SkNEW(GrAAConvexPathRenderer))->unref();
+ if (GrPathRenderer* pr = GrAAHairLinePathRenderer::Create(ctx)) {
+ chain->addPathRenderer(pr)->unref();
}
+ chain->addPathRenderer(SkNEW(GrAAConvexPathRenderer))->unref();
}
diff --git a/src/gpu/GrClipMaskCache.h b/src/gpu/GrClipMaskCache.h
index 6722d70362..9a091c4b3a 100644
--- a/src/gpu/GrClipMaskCache.h
+++ b/src/gpu/GrClipMaskCache.h
@@ -31,18 +31,22 @@ public:
}
}
- bool canReuse(const SkClipStack& clip, const GrIRect& devBounds) {
+ bool canReuse(int32_t clipGenID, const SkIRect& bounds) {
- if (fStack.empty()) {
- GrAssert(false);
+ SkASSERT(clipGenID != SkClipStack::kWideOpenGenID);
+ SkASSERT(clipGenID != SkClipStack::kEmptyGenID);
+
+ if (SkClipStack::kInvalidGenID == clipGenID) {
return false;
}
GrClipStackFrame* back = (GrClipStackFrame*) fStack.back();
+ // We could reuse the mask if bounds is a subset of last bounds. We'd have to communicate
+ // an offset to the caller.
if (back->fLastMask.texture() &&
- back->fLastBound == devBounds &&
- clip == back->fLastClip) {
+ back->fLastBound == bounds &&
+ back->fLastClipGenID == clipGenID) {
return true;
}
@@ -79,17 +83,13 @@ public:
}
}
- void getLastClip(SkClipStack* clip) const {
+ int32_t getLastClipGenID() const {
if (fStack.empty()) {
- GrAssert(false);
- clip->reset();
- return;
+ return SkClipStack::kInvalidGenID;
}
- GrClipStackFrame* back = (GrClipStackFrame*) fStack.back();
-
- *clip = back->fLastClip;
+ return ((GrClipStackFrame*) fStack.back())->fLastClipGenID;
}
GrTexture* getLastMask() {
@@ -116,7 +116,7 @@ public:
return back->fLastMask.texture();
}
- void acquireMask(const SkClipStack& clip,
+ void acquireMask(int32_t clipGenID,
const GrTextureDesc& desc,
const GrIRect& bound) {
@@ -127,7 +127,7 @@ public:
GrClipStackFrame* back = (GrClipStackFrame*) fStack.back();
- back->acquireMask(fContext, clip, desc, bound);
+ back->acquireMask(fContext, clipGenID, desc, bound);
}
int getLastMaskWidth() const {
@@ -193,20 +193,19 @@ public:
}
}
-protected:
private:
struct GrClipStackFrame {
GrClipStackFrame() {
- reset();
+ this->reset();
}
void acquireMask(GrContext* context,
- const SkClipStack& clip,
+ int32_t clipGenID,
const GrTextureDesc& desc,
const GrIRect& bound) {
- fLastClip = clip;
+ fLastClipGenID = clipGenID;
fLastMask.set(context, desc);
@@ -214,7 +213,7 @@ private:
}
void reset () {
- fLastClip.reset();
+ fLastClipGenID = SkClipStack::kInvalidGenID;
GrTextureDesc desc;
@@ -222,13 +221,12 @@ private:
fLastBound.setEmpty();
}
- SkClipStack fLastClip;
- // The mask's width & height values are used in setupDrawStateAAClip to
- // correctly scale the uvs for geometry drawn with this mask
+ int32_t fLastClipGenID;
+ // The mask's width & height values are used by GrClipMaskManager to correctly scale the
+ // texture coords for the geometry drawn with this mask.
GrAutoScratchTexture fLastMask;
- // fLastBound stores the bounding box of the clip mask in canvas
- // space. The left and top fields are used to offset the uvs for
- // geometry drawn with this mask (in setupDrawStateAAClip)
+ // fLastBound stores the bounding box of the clip mask in clip-stack space. This rect is
+ // used by GrClipMaskManager to position a rect and compute texture coords for the mask.
GrIRect fLastBound;
};
diff --git a/src/gpu/GrClipMaskManager.cpp b/src/gpu/GrClipMaskManager.cpp
index 6104f66e81..a596088ccf 100644
--- a/src/gpu/GrClipMaskManager.cpp
+++ b/src/gpu/GrClipMaskManager.cpp
@@ -7,26 +7,33 @@
*/
#include "GrClipMaskManager.h"
+#include "effects/GrTextureDomainEffect.h"
#include "GrGpu.h"
#include "GrRenderTarget.h"
#include "GrStencilBuffer.h"
#include "GrPathRenderer.h"
#include "GrPaint.h"
#include "SkRasterClip.h"
+#include "SkStrokeRec.h"
#include "GrAAConvexPathRenderer.h"
#include "GrAAHairLinePathRenderer.h"
#include "GrSWMaskHelper.h"
#include "GrCacheID.h"
+#include "SkTLazy.h"
+
GR_DEFINE_RESOURCE_CACHE_DOMAIN(GrClipMaskManager, GetAlphaMaskDomain)
#define GR_AA_CLIP 1
-#define GR_SW_CLIP 1
+
+typedef SkClipStack::Element Element;
+
+using namespace GrReducedClip;
////////////////////////////////////////////////////////////////////////////////
namespace {
// set up the draw state to enable the aa clipping mask. Besides setting up the
-// sampler matrix this also alters the vertex layout
+// stage matrix this also alters the vertex layout
void setup_drawstate_aaclip(GrGpu* gpu,
GrTexture* result,
const GrIRect &devBound) {
@@ -35,60 +42,39 @@ void setup_drawstate_aaclip(GrGpu* gpu,
static const int kMaskStage = GrPaint::kTotalStages+1;
- GrMatrix mat;
+ SkMatrix mat;
mat.setIDiv(result->width(), result->height());
mat.preTranslate(SkIntToScalar(-devBound.fLeft),
SkIntToScalar(-devBound.fTop));
mat.preConcat(drawState->getViewMatrix());
- drawState->sampler(kMaskStage)->reset();
- drawState->createTextureEffect(kMaskStage, result, mat);
+ drawState->stage(kMaskStage)->reset();
+
+ SkIRect domainTexels = SkIRect::MakeWH(devBound.width(), devBound.height());
+ // This could be a long-lived effect that is cached with the alpha-mask.
+ drawState->stage(kMaskStage)->setEffect(
+ GrTextureDomainEffect::Create(result,
+ mat,
+ GrTextureDomainEffect::MakeTexelDomain(result, domainTexels),
+ GrTextureDomainEffect::kDecal_WrapMode))->unref();
}
bool path_needs_SW_renderer(GrContext* context,
GrGpu* gpu,
- const SkPath& path,
- GrPathFill fill,
+ const SkPath& origPath,
+ const SkStrokeRec& stroke,
bool doAA) {
- // last (false) parameter disallows use of the SW path renderer
- return NULL == context->getPathRenderer(path, fill, gpu, doAA, false);
-}
-
-GrPathFill get_path_fill(const SkPath& path) {
- switch (path.getFillType()) {
- case SkPath::kWinding_FillType:
- return kWinding_GrPathFill;
- case SkPath::kEvenOdd_FillType:
- return kEvenOdd_GrPathFill;
- case SkPath::kInverseWinding_FillType:
- return kInverseWinding_GrPathFill;
- case SkPath::kInverseEvenOdd_FillType:
- return kInverseEvenOdd_GrPathFill;
- default:
- GrCrash("Unsupported path fill in clip.");
- return kWinding_GrPathFill; // suppress warning
- }
-}
-
-/**
- * Does any individual clip in 'clipIn' use anti-aliasing?
- */
-bool requires_AA(const SkClipStack& clipIn) {
-
- SkClipStack::Iter iter;
- iter.reset(clipIn, SkClipStack::Iter::kBottom_IterStart);
-
- const SkClipStack::Iter::Clip* clip = NULL;
- for (clip = iter.skipToTopmost(SkRegion::kReplace_Op);
- NULL != clip;
- clip = iter.next()) {
-
- if (clip->fDoAA) {
- return true;
- }
+ // the gpu alpha mask will draw the inverse paths as non-inverse to a temp buffer
+ SkTCopyOnFirstWrite<SkPath> path(origPath);
+ if (path->isInverseFillType()) {
+ path.writable()->toggleInverseFillType();
}
+ // last (false) parameter disallows use of the SW path renderer
+ GrPathRendererChain::DrawType type = doAA ?
+ GrPathRendererChain::kColorAntiAlias_DrawType :
+ GrPathRendererChain::kColor_DrawType;
- return false;
+ return NULL == context->getPathRenderer(*path, stroke, gpu, false, type);
}
}
@@ -98,38 +84,26 @@ bool requires_AA(const SkClipStack& clipIn) {
* will be used on any element. If so, it returns true to indicate that the
* entire clip should be rendered in SW and then uploaded en masse to the gpu.
*/
-bool GrClipMaskManager::useSWOnlyPath(const SkClipStack& clipIn) {
+bool GrClipMaskManager::useSWOnlyPath(const ElementList& elements) {
// TODO: generalize this function so that when
// a clip gets complex enough it can just be done in SW regardless
// of whether it would invoke the GrSoftwarePathRenderer.
- bool useSW = false;
-
- SkClipStack::Iter iter(clipIn, SkClipStack::Iter::kBottom_IterStart);
- const SkClipStack::Iter::Clip* clip = NULL;
-
- for (clip = iter.skipToTopmost(SkRegion::kReplace_Op);
- NULL != clip;
- clip = iter.next()) {
-
- if (SkRegion::kReplace_Op == clip->fOp) {
- // Everything before a replace op can be ignored so start
- // afresh w.r.t. determining if any element uses the SW path
- useSW = false;
- }
+ SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
+ for (ElementList::Iter iter(elements.headIter()); iter.get(); iter.next()) {
+ const Element* element = iter.get();
// rects can always be drawn directly w/o using the software path
// so only paths need to be checked
- if (NULL != clip->fPath &&
+ if (Element::kPath_Type == element->getType() &&
path_needs_SW_renderer(this->getContext(), fGpu,
- *clip->fPath,
- get_path_fill(*clip->fPath),
- clip->fDoAA)) {
- useSW = true;
+ element->getPath(),
+ stroke,
+ element->isAA())) {
+ return true;
}
}
-
- return useSW;
+ return false;
}
////////////////////////////////////////////////////////////////////////////////
@@ -138,103 +112,111 @@ bool GrClipMaskManager::useSWOnlyPath(const SkClipStack& clipIn) {
bool GrClipMaskManager::setupClipping(const GrClipData* clipDataIn) {
fCurrClipMaskType = kNone_ClipMaskType;
+ ElementList elements(16);
+ InitialState initialState;
+ SkIRect clipSpaceIBounds;
+ bool requiresAA;
+ bool isRect = false;
+
GrDrawState* drawState = fGpu->drawState();
- if (!drawState->isClipState() || clipDataIn->fClipStack->isWideOpen()) {
- fGpu->disableScissor();
- this->setGpuStencil();
- return true;
- }
- GrRenderTarget* rt = drawState->getRenderTarget();
+ const GrRenderTarget* rt = drawState->getRenderTarget();
// GrDrawTarget should have filtered this for us
GrAssert(NULL != rt);
- GrIRect devClipBounds;
- bool isIntersectionOfRects = false;
+ bool ignoreClip = !drawState->isClipState() || clipDataIn->fClipStack->isWideOpen();
+
+ if (!ignoreClip) {
+ SkIRect clipSpaceRTIBounds = SkIRect::MakeWH(rt->width(), rt->height());
+ clipSpaceRTIBounds.offset(clipDataIn->fOrigin);
+ ReduceClipStack(*clipDataIn->fClipStack,
+ clipSpaceRTIBounds,
+ &elements,
+ &initialState,
+ &clipSpaceIBounds,
+ &requiresAA);
+ if (elements.isEmpty()) {
+ if (kAllIn_InitialState == initialState) {
+ ignoreClip = clipSpaceIBounds == clipSpaceRTIBounds;
+ isRect = true;
+ } else {
+ return false;
+ }
+ }
+ }
- clipDataIn->getConservativeBounds(rt, &devClipBounds,
- &isIntersectionOfRects);
- if (devClipBounds.isEmpty()) {
- return false;
+ if (ignoreClip) {
+ fGpu->disableScissor();
+ this->setGpuStencil();
+ return true;
}
-#if GR_SW_CLIP
- bool requiresAA = requires_AA(*clipDataIn->fClipStack);
+#if GR_AA_CLIP
+ // TODO: catch isRect && requiresAA and use clip planes if available rather than a mask.
// If MSAA is enabled we can do everything in the stencil buffer.
- // Otherwise check if we should just create the entire clip mask
- // in software (this will only happen if the clip mask is anti-aliased
- // and too complex for the gpu to handle in its entirety)
- if (0 == rt->numSamples() &&
- requiresAA &&
- this->useSWOnlyPath(*clipDataIn->fClipStack)) {
- // The clip geometry is complex enough that it will be more
- // efficient to create it entirely in software
+ if (0 == rt->numSamples() && requiresAA) {
+ int32_t genID = clipDataIn->fClipStack->getTopmostGenID();
GrTexture* result = NULL;
- GrIRect devBound;
- if (this->createSoftwareClipMask(*clipDataIn, &result, &devBound)) {
- setup_drawstate_aaclip(fGpu, result, devBound);
- fGpu->disableScissor();
- this->setGpuStencil();
- return true;
- }
- // if SW clip mask creation fails fall through to the other
- // two possible methods (bottoming out at stencil clipping)
- }
-#endif // GR_SW_CLIP
+ if (this->useSWOnlyPath(elements)) {
+ // The clip geometry is complex enough that it will be more efficient to create it
+ // entirely in software
+ result = this->createSoftwareClipMask(genID,
+ initialState,
+ elements,
+ clipSpaceIBounds);
+ } else {
+ result = this->createAlphaClipMask(genID,
+ initialState,
+ elements,
+ clipSpaceIBounds);
+ }
-#if GR_AA_CLIP
- // If MSAA is enabled use the (faster) stencil path for AA clipping
- // otherwise the alpha clip mask is our only option
- if (0 == rt->numSamples() && requiresAA) {
- // Since we are going to create a destination texture of the correct
- // size for the mask (rather than being bound by the size of the
- // render target) we aren't going to use scissoring like the stencil
- // path does (see scissorSettings below)
- GrTexture* result = NULL;
- GrIRect devBound;
- if (this->createAlphaClipMask(*clipDataIn, &result, &devBound)) {
- setup_drawstate_aaclip(fGpu, result, devBound);
+ if (NULL != result) {
+ // The mask's top left coord should be pinned to the rounded-out top left corner of
+ // clipSpace bounds. We determine the mask's position WRT to the render target here.
+ SkIRect rtSpaceMaskBounds = clipSpaceIBounds;
+ rtSpaceMaskBounds.offset(-clipDataIn->fOrigin);
+ setup_drawstate_aaclip(fGpu, result, rtSpaceMaskBounds);
fGpu->disableScissor();
this->setGpuStencil();
return true;
}
-
- // if alpha clip mask creation fails fall through to the stencil
- // buffer method
+ // if alpha clip mask creation fails fall through to the non-AA code paths
}
#endif // GR_AA_CLIP
- // Either a hard (stencil buffer) clip was explicitly requested or
- // an antialiased clip couldn't be created. In either case, free up
- // the texture in the antialiased mask cache.
- // TODO: this may require more investigation. Ganesh performs a lot of
- // utility draws (e.g., clears, InOrderDrawBuffer playbacks) that hit
- // the stencil buffer path. These may be "incorrectly" clearing the
- // AA cache.
+ // Either a hard (stencil buffer) clip was explicitly requested or an anti-aliased clip couldn't
+ // be created. In either case, free up the texture in the anti-aliased mask cache.
+ // TODO: this may require more investigation. Ganesh performs a lot of utility draws (e.g.,
+ // clears, InOrderDrawBuffer playbacks) that hit the stencil buffer path. These may be
+ // "incorrectly" clearing the AA cache.
fAACache.reset();
// If the clip is a rectangle then just set the scissor. Otherwise, create
// a stencil mask.
- if (isIntersectionOfRects) {
- fGpu->enableScissor(devClipBounds);
+ if (isRect) {
+ SkIRect clipRect = clipSpaceIBounds;
+ clipRect.offset(-clipDataIn->fOrigin);
+ fGpu->enableScissor(clipRect);
this->setGpuStencil();
return true;
}
// use the stencil clip if we can't represent the clip as a rectangle.
- bool useStencil = !clipDataIn->fClipStack->isWideOpen() &&
- !devClipBounds.isEmpty();
-
- if (useStencil) {
- this->createStencilClipMask(*clipDataIn, devClipBounds);
- }
- // This must occur after createStencilClipMask. That function may change
- // the scissor. Also, it only guarantees that the stencil mask is correct
- // within the bounds it was passed, so we must use both stencil and scissor
- // test to the bounds for the final draw.
- fGpu->enableScissor(devClipBounds);
+ SkIPoint clipSpaceToStencilSpaceOffset = -clipDataIn->fOrigin;
+ this->createStencilClipMask(initialState,
+ elements,
+ clipSpaceIBounds,
+ clipSpaceToStencilSpaceOffset);
+
+ // This must occur after createStencilClipMask. That function may change the scissor. Also, it
+ // only guarantees that the stencil mask is correct within the bounds it was passed, so we must
+ // use both stencil and scissor test to the bounds for the final draw.
+ SkIRect scissorSpaceIBounds(clipSpaceIBounds);
+ scissorSpaceIBounds.offset(clipSpaceToStencilSpaceOffset);
+ fGpu->enableScissor(scissorSpaceIBounds);
this->setGpuStencil();
return true;
}
@@ -242,134 +224,14 @@ bool GrClipMaskManager::setupClipping(const GrClipData* clipDataIn) {
#define VISUALIZE_COMPLEX_CLIP 0
#if VISUALIZE_COMPLEX_CLIP
- #include "GrRandom.h"
- GrRandom gRandom;
+ #include "SkRandom.h"
+ SkRandom gRandom;
#define SET_RANDOM_COLOR drawState->setColor(0xff000000 | gRandom.nextU());
#else
#define SET_RANDOM_COLOR
#endif
namespace {
-/**
- * Does "canvContainer" contain "devContainee"? If either is empty then
- * no containment is possible. "canvContainer" is in canvas coordinates while
- * "devContainee" is in device coordiates. "origin" provides the mapping between
- * the two.
- */
-bool contains(const SkRect& canvContainer,
- const SkIRect& devContainee,
- const SkIPoint& origin) {
- return !devContainee.isEmpty() && !canvContainer.isEmpty() &&
- canvContainer.fLeft <= SkIntToScalar(devContainee.fLeft+origin.fX) &&
- canvContainer.fTop <= SkIntToScalar(devContainee.fTop+origin.fY) &&
- canvContainer.fRight >= SkIntToScalar(devContainee.fRight+origin.fX) &&
- canvContainer.fBottom >= SkIntToScalar(devContainee.fBottom+origin.fY);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// determines how many elements at the head of the clip can be skipped and
-// whether the initial clear should be to the inside- or outside-the-clip value,
-// and what op should be used to draw the first element that isn't skipped.
-const SkClipStack::Iter::Clip* process_initial_clip_elements(
- SkClipStack::Iter* iter,
- const GrIRect& devBounds,
- bool* clearToInside,
- SkRegion::Op* firstOp,
- const GrClipData& clipData) {
-
- GrAssert(NULL != iter && NULL != clearToInside && NULL != firstOp);
-
- // logically before the first element of the clip stack is
- // processed the clip is entirely open. However, depending on the
- // first set op we may prefer to clear to 0 for performance. We may
- // also be able to skip the initial clip paths/rects. We loop until
- // we cannot skip an element.
- bool done = false;
- *clearToInside = true;
-
- const SkClipStack::Iter::Clip* clip = NULL;
-
- for (clip = iter->skipToTopmost(SkRegion::kReplace_Op);
- NULL != clip && !done;
- clip = iter->next()) {
- switch (clip->fOp) {
- case SkRegion::kReplace_Op:
- // replace ignores everything previous
- *firstOp = SkRegion::kReplace_Op;
- *clearToInside = false;
- done = true;
- break;
- case SkRegion::kIntersect_Op:
- // if this element contains the entire bounds then we
- // can skip it.
- if (NULL != clip->fRect &&
- contains(*clip->fRect, devBounds, clipData.fOrigin)) {
- break;
- }
- // if everything is initially clearToInside then intersect is
- // same as clear to 0 and treat as a replace. Otherwise,
- // set stays empty.
- if (*clearToInside) {
- *firstOp = SkRegion::kReplace_Op;
- *clearToInside = false;
- done = true;
- }
- break;
- // we can skip a leading union.
- case SkRegion::kUnion_Op:
- // if everything is initially outside then union is
- // same as replace. Otherwise, every pixel is still
- // clearToInside
- if (!*clearToInside) {
- *firstOp = SkRegion::kReplace_Op;
- done = true;
- }
- break;
- case SkRegion::kXOR_Op:
- // xor is same as difference or replace both of which
- // can be 1-pass instead of 2 for xor.
- if (*clearToInside) {
- *firstOp = SkRegion::kDifference_Op;
- } else {
- *firstOp = SkRegion::kReplace_Op;
- }
- done = true;
- break;
- case SkRegion::kDifference_Op:
- // if all pixels are clearToInside then we have to process the
- // difference, otherwise it has no effect and all pixels
- // remain outside.
- if (*clearToInside) {
- *firstOp = SkRegion::kDifference_Op;
- done = true;
- }
- break;
- case SkRegion::kReverseDifference_Op:
- // if all pixels are clearToInside then reverse difference
- // produces empty set. Otherise it is same as replace
- if (*clearToInside) {
- *clearToInside = false;
- } else {
- *firstOp = SkRegion::kReplace_Op;
- done = true;
- }
- break;
- default:
- GrCrash("Unknown set op.");
- }
-
- if (done) {
- // we need to break out here (rather than letting the test in
- // the loop do it) since backing up the iterator is very expensive
- break;
- }
- }
- return clip;
-}
-
-}
-
-namespace {
////////////////////////////////////////////////////////////////////////////////
// set up the OpenGL blend function to perform the specified
@@ -405,13 +267,14 @@ void setup_boolean_blendcoeffs(GrDrawState* drawState, SkRegion::Op op) {
bool draw_path_in_software(GrContext* context,
GrGpu* gpu,
const SkPath& path,
- GrPathFill fill,
bool doAA,
const GrIRect& resultBounds) {
+ SkStrokeRec rec(SkStrokeRec::kFill_InitStyle);
SkAutoTUnref<GrTexture> texture(
GrSWMaskHelper::DrawPathMaskToTexture(context, path,
- resultBounds, fill,
+ rec,
+ resultBounds,
doAA, NULL));
if (NULL == texture) {
return false;
@@ -422,92 +285,115 @@ bool draw_path_in_software(GrContext* context,
GrSWMaskHelper::DrawToTargetWithPathMask(texture, gpu, rect);
- GrAssert(!GrIsFillInverted(fill));
- return true;
-}
-
-
-////////////////////////////////////////////////////////////////////////////////
-bool draw_path(GrContext* context,
- GrGpu* gpu,
- const SkPath& path,
- GrPathFill fill,
- bool doAA,
- const GrIRect& resultBounds) {
-
- GrPathRenderer* pr = context->getPathRenderer(path, fill, gpu, doAA, false);
- if (NULL == pr) {
- return draw_path_in_software(context, gpu, path, fill, doAA, resultBounds);
- }
-
- pr->drawPath(path, fill, gpu, doAA);
+ GrAssert(!path.isInverseFillType());
return true;
}
-
-// 'rect' enters in device coordinates and leaves in canvas coordinates
-void device_to_canvas(SkRect* rect, const SkIPoint& origin) {
- GrAssert(NULL != rect);
-
- rect->fLeft += SkIntToScalar(origin.fX);
- rect->fTop += SkIntToScalar(origin.fY);
- rect->fRight += SkIntToScalar(origin.fX);
- rect->fBottom += SkIntToScalar(origin.fY);
-}
-
}
////////////////////////////////////////////////////////////////////////////////
-bool GrClipMaskManager::drawClipShape(GrTexture* target,
- const SkClipStack::Iter::Clip* clip,
- const GrIRect& resultBounds) {
+bool GrClipMaskManager::drawElement(GrTexture* target,
+ const SkClipStack::Element* element,
+ GrPathRenderer* pr) {
GrDrawState* drawState = fGpu->drawState();
- GrAssert(NULL != drawState);
drawState->setRenderTarget(target->asRenderTarget());
- if (NULL != clip->fRect) {
- if (clip->fDoAA) {
- getContext()->getAARectRenderer()->fillAARect(fGpu, fGpu,
- *clip->fRect,
- true);
- } else {
- fGpu->drawSimpleRect(*clip->fRect, NULL);
+ switch (element->getType()) {
+ case Element::kRect_Type:
+ // TODO: Do rects directly to the accumulator using a aa-rect GrEffect that covers the
+ // entire mask bounds and writes 0 outside the rect.
+ if (element->isAA()) {
+ getContext()->getAARectRenderer()->fillAARect(fGpu,
+ fGpu,
+ element->getRect(),
+ false);
+ } else {
+ fGpu->drawSimpleRect(element->getRect(), NULL);
+ }
+ return true;
+ case Element::kPath_Type: {
+ SkTCopyOnFirstWrite<SkPath> path(element->getPath());
+ if (path->isInverseFillType()) {
+ path.writable()->toggleInverseFillType();
+ }
+ SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
+ if (NULL == pr) {
+ GrPathRendererChain::DrawType type;
+ type = element->isAA() ? GrPathRendererChain::kColorAntiAlias_DrawType :
+ GrPathRendererChain::kColor_DrawType;
+ pr = this->getContext()->getPathRenderer(*path, stroke, fGpu, false, type);
+ }
+ if (NULL == pr) {
+ return false;
+ }
+ pr->drawPath(element->getPath(), stroke, fGpu, element->isAA());
+ break;
}
- } else if (NULL != clip->fPath) {
- return draw_path(this->getContext(), fGpu,
- *clip->fPath,
- get_path_fill(*clip->fPath),
- clip->fDoAA,
- resultBounds);
+ default:
+ // something is wrong if we're trying to draw an empty element.
+ GrCrash("Unexpected element type");
+ return false;
}
return true;
}
-void GrClipMaskManager::drawTexture(GrTexture* target,
- GrTexture* texture) {
+bool GrClipMaskManager::canStencilAndDrawElement(GrTexture* target,
+ const SkClipStack::Element* element,
+ GrPathRenderer** pr) {
GrDrawState* drawState = fGpu->drawState();
- GrAssert(NULL != drawState);
-
- // no AA here since it is encoded in the texture
drawState->setRenderTarget(target->asRenderTarget());
- GrMatrix sampleM;
- sampleM.setIDiv(texture->width(), texture->height());
+ switch (element->getType()) {
+ case Element::kRect_Type:
+ return true;
+ case Element::kPath_Type: {
+ SkTCopyOnFirstWrite<SkPath> path(element->getPath());
+ if (path->isInverseFillType()) {
+ path.writable()->toggleInverseFillType();
+ }
+ SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
+ GrPathRendererChain::DrawType type = element->isAA() ?
+ GrPathRendererChain::kStencilAndColorAntiAlias_DrawType :
+ GrPathRendererChain::kStencilAndColor_DrawType;
+ *pr = this->getContext()->getPathRenderer(*path, stroke, fGpu, false, type);
+ return NULL != *pr;
+ }
+ default:
+ // something is wrong if we're trying to draw an empty element.
+ GrCrash("Unexpected element type");
+ return false;
+ }
+}
+
+void GrClipMaskManager::mergeMask(GrTexture* dstMask,
+ GrTexture* srcMask,
+ SkRegion::Op op,
+ const GrIRect& dstBound,
+ const GrIRect& srcBound) {
+ GrDrawState* drawState = fGpu->drawState();
+ SkMatrix oldMatrix = drawState->getViewMatrix();
+ drawState->viewMatrix()->reset();
- drawState->createTextureEffect(0, texture, sampleM);
+ drawState->setRenderTarget(dstMask->asRenderTarget());
- GrRect rect = GrRect::MakeWH(SkIntToScalar(target->width()),
- SkIntToScalar(target->height()));
+ setup_boolean_blendcoeffs(drawState, op);
- fGpu->drawSimpleRect(rect, NULL);
+ SkMatrix sampleM;
+ sampleM.setIDiv(srcMask->width(), srcMask->height());
+ drawState->stage(0)->setEffect(
+ GrTextureDomainEffect::Create(srcMask,
+ sampleM,
+ GrTextureDomainEffect::MakeTexelDomain(srcMask, srcBound),
+ GrTextureDomainEffect::kDecal_WrapMode))->unref();
+ fGpu->drawSimpleRect(SkRect::MakeFromIRect(dstBound), NULL);
drawState->disableStage(0);
+ drawState->setViewMatrix(oldMatrix);
}
// get a texture to act as a temporary buffer for AA clip boolean operations
// TODO: given the expense of createTexture we may want to just cache this too
-void GrClipMaskManager::getTemp(const GrIRect& bounds,
- GrAutoScratchTexture* temp) {
+void GrClipMaskManager::getTemp(int width, int height, GrAutoScratchTexture* temp) {
if (NULL != temp->texture()) {
// we've already allocated the temp texture
return;
@@ -515,83 +401,58 @@ void GrClipMaskManager::getTemp(const GrIRect& bounds,
GrTextureDesc desc;
desc.fFlags = kRenderTarget_GrTextureFlagBit|kNoStencil_GrTextureFlagBit;
- desc.fWidth = bounds.width();
- desc.fHeight = bounds.height();
+ desc.fWidth = width;
+ desc.fHeight = height;
desc.fConfig = kAlpha_8_GrPixelConfig;
temp->set(this->getContext(), desc);
}
-
-void GrClipMaskManager::setupCache(const SkClipStack& clipIn,
- const GrIRect& bounds) {
- // Since we are setting up the cache we know the last lookup was a miss
- // Free up the currently cached mask so it can be reused
- fAACache.reset();
-
- GrTextureDesc desc;
- desc.fFlags = kRenderTarget_GrTextureFlagBit|kNoStencil_GrTextureFlagBit;
- desc.fWidth = bounds.width();
- desc.fHeight = bounds.height();
- desc.fConfig = kAlpha_8_GrPixelConfig;
-
- fAACache.acquireMask(clipIn, desc, bounds);
-}
-
////////////////////////////////////////////////////////////////////////////////
-// Shared preamble between gpu and SW-only AA clip mask creation paths.
-// Handles caching, determination of clip mask bound & allocation (if needed)
-// of the result texture
-// Returns true if there is no more work to be done (i.e., we got a cache hit)
-bool GrClipMaskManager::clipMaskPreamble(const GrClipData& clipDataIn,
- GrTexture** result,
- GrIRect* devResultBounds) {
- GrDrawState* origDrawState = fGpu->drawState();
- GrAssert(origDrawState->isClipState());
-
- GrRenderTarget* rt = origDrawState->getRenderTarget();
- GrAssert(NULL != rt);
-
- // unlike the stencil path the alpha path is not bound to the size of the
- // render target - determine the minimum size required for the mask
- // Note: intBounds is in device (as opposed to canvas) coordinates
- clipDataIn.getConservativeBounds(rt, devResultBounds);
-
- // need to outset a pixel since the standard bounding box computation
- // path doesn't leave any room for antialiasing (esp. w.r.t. rects)
- devResultBounds->outset(1, 1);
+// Handles caching & allocation (if needed) of a clip alpha-mask texture for both the sw-upload
+// or gpu-rendered cases. Returns true if there is no more work to be done (i.e., we got a cache
+// hit)
+bool GrClipMaskManager::getMaskTexture(int32_t clipStackGenID,
+ const SkIRect& clipSpaceIBounds,
+ GrTexture** result) {
+ bool cached = fAACache.canReuse(clipStackGenID, clipSpaceIBounds);
+ if (!cached) {
+
+ // There isn't a suitable entry in the cache so we create a new texture to store the mask.
+ // Since we are setting up the cache we know the last lookup was a miss. Free up the
+ // currently cached mask so it can be reused.
+ fAACache.reset();
- // TODO: make sure we don't outset if bounds are still 0,0 @ min
+ GrTextureDesc desc;
+ desc.fFlags = kRenderTarget_GrTextureFlagBit;
+ desc.fWidth = clipSpaceIBounds.width();
+ desc.fHeight = clipSpaceIBounds.height();
+ desc.fConfig = kAlpha_8_GrPixelConfig;
- if (fAACache.canReuse(*clipDataIn.fClipStack, *devResultBounds)) {
- *result = fAACache.getLastMask();
- fAACache.getLastBound(devResultBounds);
- return true;
+ fAACache.acquireMask(clipStackGenID, desc, clipSpaceIBounds);
}
- this->setupCache(*clipDataIn.fClipStack, *devResultBounds);
- return false;
+ *result = fAACache.getLastMask();
+ return cached;
}
////////////////////////////////////////////////////////////////////////////////
// Create a 8-bit clip mask in alpha
-bool GrClipMaskManager::createAlphaClipMask(const GrClipData& clipDataIn,
- GrTexture** result,
- GrIRect *devResultBounds) {
- GrAssert(NULL != devResultBounds);
+GrTexture* GrClipMaskManager::createAlphaClipMask(int32_t clipStackGenID,
+ InitialState initialState,
+ const ElementList& elements,
+ const SkIRect& clipSpaceIBounds) {
GrAssert(kNone_ClipMaskType == fCurrClipMaskType);
- if (this->clipMaskPreamble(clipDataIn, result, devResultBounds)) {
+ GrTexture* result;
+ if (this->getMaskTexture(clipStackGenID, clipSpaceIBounds, &result)) {
fCurrClipMaskType = kAlpha_ClipMaskType;
- return true;
+ return result;
}
- // Note: 'resultBounds' is in device (as opposed to canvas) coordinates
-
- GrTexture* accum = fAACache.getLastMask();
- if (NULL == accum) {
+ if (NULL == result) {
fAACache.reset();
- return false;
+ return NULL;
}
GrDrawTarget::AutoStateRestore asr(fGpu, GrDrawTarget::kReset_ASRInit);
@@ -599,108 +460,135 @@ bool GrClipMaskManager::createAlphaClipMask(const GrClipData& clipDataIn,
GrDrawTarget::AutoGeometryPush agp(fGpu);
- if (0 != devResultBounds->fTop || 0 != devResultBounds->fLeft ||
- 0 != clipDataIn.fOrigin.fX || 0 != clipDataIn.fOrigin.fY) {
- // if we were able to trim down the size of the mask we need to
- // offset the paths & rects that will be used to compute it
- drawState->viewMatrix()->setTranslate(
- SkIntToScalar(-devResultBounds->fLeft-clipDataIn.fOrigin.fX),
- SkIntToScalar(-devResultBounds->fTop-clipDataIn.fOrigin.fY));
- }
+ // The top-left of the mask corresponds to the top-left corner of the bounds.
+ SkVector clipToMaskOffset = {
+ SkIntToScalar(-clipSpaceIBounds.fLeft),
+ SkIntToScalar(-clipSpaceIBounds.fTop)
+ };
+ // The texture may be larger than necessary, this rect represents the part of the texture
+ // we populate with a rasterization of the clip.
+ SkIRect maskSpaceIBounds = SkIRect::MakeWH(clipSpaceIBounds.width(), clipSpaceIBounds.height());
+
+ // We're drawing a coverage mask and want coverage to be run through the blend function.
+ drawState->enableState(GrDrawState::kCoverageDrawing_StateBit);
- bool clearToInside;
- SkRegion::Op firstOp = SkRegion::kReplace_Op; // suppress warning
+ // Set the matrix so that rendered clip elements are transformed to mask space from clip space.
+ drawState->viewMatrix()->setTranslate(clipToMaskOffset);
- SkClipStack::Iter iter(*clipDataIn.fClipStack,
- SkClipStack::Iter::kBottom_IterStart);
- const SkClipStack::Iter::Clip* clip = process_initial_clip_elements(&iter,
- *devResultBounds,
- &clearToInside,
- &firstOp,
- clipDataIn);
+ // The scratch texture that we are drawing into can be substantially larger than the mask. Only
+ // clear the part that we care about.
+ fGpu->clear(&maskSpaceIBounds,
+ kAllIn_InitialState == initialState ? 0xffffffff : 0x00000000,
+ result->asRenderTarget());
- fGpu->clear(NULL,
- clearToInside ? 0xffffffff : 0x00000000,
- accum->asRenderTarget());
+ // When we use the stencil in the below loop it is important to have this clip installed.
+ // The second pass that zeros the stencil buffer renders the rect maskSpaceIBounds so the first
+ // pass must not set values outside of this bounds or stencil values outside the rect won't be
+ // cleared.
+ GrDrawTarget::AutoClipRestore acr(fGpu, maskSpaceIBounds);
+ drawState->enableState(GrDrawState::kClip_StateBit);
GrAutoScratchTexture temp;
- bool first = true;
// walk through each clip element and perform its set op
- for ( ; NULL != clip; clip = iter.next()) {
+ for (ElementList::Iter iter = elements.headIter(); iter.get(); iter.next()) {
+ const Element* element = iter.get();
+ SkRegion::Op op = element->getOp();
+ bool invert = element->isInverseFilled();
- SkRegion::Op op = clip->fOp;
- if (first) {
- first = false;
- op = firstOp;
- }
-
- if (SkRegion::kReplace_Op == op) {
- // clear the accumulator and draw the new object directly into it
- fGpu->clear(NULL, 0x00000000, accum->asRenderTarget());
+ if (invert || SkRegion::kIntersect_Op == op || SkRegion::kReverseDifference_Op == op) {
+ GrPathRenderer* pr = NULL;
+ bool useTemp = !this->canStencilAndDrawElement(result, element, &pr);
+ GrTexture* dst;
+ // This is the bounds of the clip element in the space of the alpha-mask. The temporary
+ // mask buffer can be substantially larger than the actually clip stack element. We
+ // touch the minimum number of pixels necessary and use decal mode to combine it with
+ // the accumulator.
+ GrIRect maskSpaceElementIBounds;
+
+ if (useTemp) {
+ if (invert) {
+ maskSpaceElementIBounds = maskSpaceIBounds;
+ } else {
+ GrRect elementBounds = element->getBounds();
+ elementBounds.offset(clipToMaskOffset);
+ elementBounds.roundOut(&maskSpaceElementIBounds);
+ }
- setup_boolean_blendcoeffs(drawState, op);
- this->drawClipShape(accum, clip, *devResultBounds);
-
- } else if (SkRegion::kReverseDifference_Op == op ||
- SkRegion::kIntersect_Op == op) {
- // there is no point in intersecting a screen filling rectangle.
- if (SkRegion::kIntersect_Op == op && NULL != clip->fRect &&
- contains(*clip->fRect, *devResultBounds, clipDataIn.fOrigin)) {
- continue;
- }
+ this->getTemp(maskSpaceIBounds.fRight, maskSpaceIBounds.fBottom, &temp);
+ if (NULL == temp.texture()) {
+ fAACache.reset();
+ return NULL;
+ }
+ dst = temp.texture();
+ // clear the temp target and set blend to replace
+ fGpu->clear(&maskSpaceElementIBounds,
+ invert ? 0xffffffff : 0x00000000,
+ dst->asRenderTarget());
+ setup_boolean_blendcoeffs(drawState, SkRegion::kReplace_Op);
- getTemp(*devResultBounds, &temp);
- if (NULL == temp.texture()) {
- fAACache.reset();
- return false;
+ } else {
+ // draw directly into the result with the stencil set to make the pixels affected
+ // by the clip shape be non-zero.
+ dst = result;
+ GR_STATIC_CONST_SAME_STENCIL(kStencilInElement,
+ kReplace_StencilOp,
+ kReplace_StencilOp,
+ kAlways_StencilFunc,
+ 0xffff,
+ 0xffff,
+ 0xffff);
+ drawState->setStencil(kStencilInElement);
+ setup_boolean_blendcoeffs(drawState, op);
}
- // clear the temp target & draw into it
- fGpu->clear(NULL, 0x00000000, temp.texture()->asRenderTarget());
-
- setup_boolean_blendcoeffs(drawState, SkRegion::kReplace_Op);
- this->drawClipShape(temp.texture(), clip, *devResultBounds);
+ drawState->setAlpha(invert ? 0x00 : 0xff);
- // TODO: rather than adding these two translations here
- // compute the bounding box needed to render the texture
- // into temp
- if (0 != devResultBounds->fTop || 0 != devResultBounds->fLeft ||
- 0 != clipDataIn.fOrigin.fX || 0 != clipDataIn.fOrigin.fY) {
- // In order for the merge of the temp clip into the accumulator
- // to work we need to disable the translation
- drawState->viewMatrix()->reset();
+ if (!this->drawElement(dst, element, pr)) {
+ fAACache.reset();
+ return NULL;
}
- // Now draw into the accumulator using the real operation
- // and the temp buffer as a texture
- setup_boolean_blendcoeffs(drawState, op);
- this->drawTexture(accum, temp.texture());
-
- if (0 != devResultBounds->fTop || 0 != devResultBounds->fLeft ||
- 0 != clipDataIn.fOrigin.fX || 0 != clipDataIn.fOrigin.fY) {
- drawState->viewMatrix()->setTranslate(
- SkIntToScalar(-devResultBounds->fLeft-clipDataIn.fOrigin.fX),
- SkIntToScalar(-devResultBounds->fTop-clipDataIn.fOrigin.fY));
+ if (useTemp) {
+ // Now draw into the accumulator using the real operation and the temp buffer as a
+ // texture
+ this->mergeMask(result,
+ temp.texture(),
+ op,
+ maskSpaceIBounds,
+ maskSpaceElementIBounds);
+ } else {
+ // Draw to the exterior pixels (those with a zero stencil value).
+ drawState->setAlpha(invert ? 0xff : 0x00);
+ GR_STATIC_CONST_SAME_STENCIL(kDrawOutsideElement,
+ kZero_StencilOp,
+ kZero_StencilOp,
+ kEqual_StencilFunc,
+ 0xffff,
+ 0x0000,
+ 0xffff);
+ drawState->setStencil(kDrawOutsideElement);
+ fGpu->drawSimpleRect(clipSpaceIBounds);
+ drawState->disableStencil();
}
-
} else {
- // all the remaining ops can just be directly draw into
- // the accumulation buffer
+ // all the remaining ops can just be directly draw into the accumulation buffer
+ drawState->setAlpha(0xff);
setup_boolean_blendcoeffs(drawState, op);
- this->drawClipShape(accum, clip, *devResultBounds);
+ this->drawElement(result, element);
}
}
- *result = accum;
fCurrClipMaskType = kAlpha_ClipMaskType;
- return true;
+ return result;
}
////////////////////////////////////////////////////////////////////////////////
// Create a 1-bit clip mask in the stencil buffer. 'devClipBounds' are in device
// (as opposed to canvas) coordinates
-bool GrClipMaskManager::createStencilClipMask(const GrClipData& clipDataIn,
- const GrIRect& devClipBounds) {
+bool GrClipMaskManager::createStencilClipMask(InitialState initialState,
+ const ElementList& elements,
+ const SkIRect& clipSpaceIBounds,
+ const SkIPoint& clipSpaceToStencilOffset) {
GrAssert(kNone_ClipMaskType == fCurrClipMaskType);
@@ -715,147 +603,125 @@ bool GrClipMaskManager::createStencilClipMask(const GrClipData& clipDataIn,
if (NULL == stencilBuffer) {
return false;
}
+ int32_t genID = elements.tail()->getGenID();
- if (stencilBuffer->mustRenderClip(clipDataIn, rt->width(), rt->height())) {
+ if (stencilBuffer->mustRenderClip(genID, clipSpaceIBounds, clipSpaceToStencilOffset)) {
- stencilBuffer->setLastClip(clipDataIn, rt->width(), rt->height());
-
- // we set the current clip to the bounds so that our recursive
- // draws are scissored to them. We use the copy of the complex clip
- // we just stashed on the SB to render from. We set it back after
- // we finish drawing it into the stencil.
- const GrClipData* oldClipData = fGpu->getClip();
-
- // The origin of 'newClipData' is (0, 0) so it is okay to place
- // a device-coordinate bound in 'newClipStack'
- SkClipStack newClipStack(devClipBounds);
- GrClipData newClipData;
- newClipData.fClipStack = &newClipStack;
-
- fGpu->setClip(&newClipData);
+ stencilBuffer->setLastClip(genID, clipSpaceIBounds, clipSpaceToStencilOffset);
GrDrawTarget::AutoStateRestore asr(fGpu, GrDrawTarget::kReset_ASRInit);
drawState = fGpu->drawState();
drawState->setRenderTarget(rt);
GrDrawTarget::AutoGeometryPush agp(fGpu);
- if (0 != clipDataIn.fOrigin.fX || 0 != clipDataIn.fOrigin.fY) {
- // Add the saveLayer's offset to the view matrix rather than
- // offset each individual draw
- drawState->viewMatrix()->setTranslate(
- SkIntToScalar(-clipDataIn.fOrigin.fX),
- SkIntToScalar(-clipDataIn.fOrigin.fY));
- }
+ // We set the current clip to the bounds so that our recursive draws are scissored to them.
+ SkIRect stencilSpaceIBounds(clipSpaceIBounds);
+ stencilSpaceIBounds.offset(clipSpaceToStencilOffset);
+ GrDrawTarget::AutoClipRestore acr(fGpu, stencilSpaceIBounds);
+ drawState->enableState(GrDrawState::kClip_StateBit);
+
+ // Set the matrix so that rendered clip elements are transformed from clip to stencil space.
+ SkVector translate = {
+ SkIntToScalar(clipSpaceToStencilOffset.fX),
+ SkIntToScalar(clipSpaceToStencilOffset.fY)
+ };
+ drawState->viewMatrix()->setTranslate(translate);
#if !VISUALIZE_COMPLEX_CLIP
drawState->enableState(GrDrawState::kNoColorWrites_StateBit);
#endif
int clipBit = stencilBuffer->bits();
- SkASSERT((clipBit <= 16) &&
- "Ganesh only handles 16b or smaller stencil buffers");
+ SkASSERT((clipBit <= 16) && "Ganesh only handles 16b or smaller stencil buffers");
clipBit = (1 << (clipBit-1));
GrIRect devRTRect = GrIRect::MakeWH(rt->width(), rt->height());
- bool clearToInside;
- SkRegion::Op firstOp = SkRegion::kReplace_Op; // suppress warning
-
- SkClipStack::Iter iter(*oldClipData->fClipStack,
- SkClipStack::Iter::kBottom_IterStart);
- const SkClipStack::Iter::Clip* clip = process_initial_clip_elements(&iter,
- devRTRect,
- &clearToInside,
- &firstOp,
- clipDataIn);
-
- fGpu->clearStencilClip(devClipBounds, clearToInside);
- bool first = true;
+ fGpu->clearStencilClip(stencilSpaceIBounds, kAllIn_InitialState == initialState);
// walk through each clip element and perform its set op
// with the existing clip.
- for ( ; NULL != clip; clip = iter.next()) {
- GrPathFill fill;
+ for (ElementList::Iter iter(elements.headIter()); NULL != iter.get(); iter.next()) {
+ const Element* element = iter.get();
+ SkPath::FillType fill;
bool fillInverted = false;
// enabled at bottom of loop
drawState->disableState(GrGpu::kModifyStencilClip_StateBit);
// if the target is MSAA then we want MSAA enabled when the clip is soft
if (rt->isMultisampled()) {
- drawState->setState(GrDrawState::kHWAntialias_StateBit, clip->fDoAA);
+ drawState->setState(GrDrawState::kHWAntialias_StateBit, element->isAA());
}
- // Can the clip element be drawn directly to the stencil buffer
- // with a non-inverted fill rule without extra passes to
- // resolve in/out status?
- bool canRenderDirectToStencil = false;
+ // This will be used to determine whether the clip shape can be rendered into the
+ // stencil with arbitrary stencil settings.
+ GrPathRenderer::StencilSupport stencilSupport;
- SkRegion::Op op = clip->fOp;
- if (first) {
- first = false;
- op = firstOp;
- }
+ SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
+
+ SkRegion::Op op = element->getOp();
GrPathRenderer* pr = NULL;
- const SkPath* clipPath = NULL;
- if (NULL != clip->fRect) {
- canRenderDirectToStencil = true;
- fill = kEvenOdd_GrPathFill;
+ SkTCopyOnFirstWrite<SkPath> clipPath;
+ if (Element::kRect_Type == element->getType()) {
+ stencilSupport = GrPathRenderer::kNoRestriction_StencilSupport;
+ fill = SkPath::kEvenOdd_FillType;
fillInverted = false;
- // there is no point in intersecting a screen filling
- // rectangle.
- if (SkRegion::kIntersect_Op == op &&
- contains(*clip->fRect, devRTRect, oldClipData->fOrigin)) {
- continue;
- }
} else {
- GrAssert(NULL != clip->fPath);
- fill = get_path_fill(*clip->fPath);
- fillInverted = GrIsFillInverted(fill);
- fill = GrNonInvertedFill(fill);
- clipPath = clip->fPath;
- pr = this->getContext()->getPathRenderer(*clipPath, fill, fGpu, false, true);
+ GrAssert(Element::kPath_Type == element->getType());
+ clipPath.init(element->getPath());
+ fill = clipPath->getFillType();
+ fillInverted = clipPath->isInverseFillType();
+ if (fillInverted) {
+ clipPath.writable()->toggleInverseFillType();
+ fill = clipPath->getFillType();
+ }
+ pr = this->getContext()->getPathRenderer(*clipPath,
+ stroke,
+ fGpu,
+ false,
+ GrPathRendererChain::kStencilOnly_DrawType,
+ &stencilSupport);
if (NULL == pr) {
- fGpu->setClip(oldClipData);
return false;
}
- canRenderDirectToStencil =
- !pr->requiresStencilPass(*clipPath, fill, fGpu);
}
int passes;
GrStencilSettings stencilSettings[GrStencilSettings::kMaxStencilClipPasses];
+ bool canRenderDirectToStencil =
+ GrPathRenderer::kNoRestriction_StencilSupport == stencilSupport;
bool canDrawDirectToClip; // Given the renderer, the element,
- // fill rule, and set operation can
- // we render the element directly to
- // stencil bit used for clipping.
- canDrawDirectToClip =
- GrStencilSettings::GetClipPasses(op,
- canRenderDirectToStencil,
- clipBit,
- fillInverted,
- &passes,
- stencilSettings);
+ // fill rule, and set operation can
+ // we render the element directly to
+ // stencil bit used for clipping.
+ canDrawDirectToClip = GrStencilSettings::GetClipPasses(op,
+ canRenderDirectToStencil,
+ clipBit,
+ fillInverted,
+ &passes,
+ stencilSettings);
// draw the element to the client stencil bits if necessary
if (!canDrawDirectToClip) {
GR_STATIC_CONST_SAME_STENCIL(gDrawToStencil,
- kIncClamp_StencilOp,
- kIncClamp_StencilOp,
- kAlways_StencilFunc,
- 0xffff,
- 0x0000,
- 0xffff);
+ kIncClamp_StencilOp,
+ kIncClamp_StencilOp,
+ kAlways_StencilFunc,
+ 0xffff,
+ 0x0000,
+ 0xffff);
SET_RANDOM_COLOR
- if (NULL != clip->fRect) {
+ if (Element::kRect_Type == element->getType()) {
*drawState->stencil() = gDrawToStencil;
- fGpu->drawSimpleRect(*clip->fRect, NULL);
+ fGpu->drawSimpleRect(element->getRect(), NULL);
} else {
+ GrAssert(Element::kPath_Type == element->getType());
if (canRenderDirectToStencil) {
*drawState->stencil() = gDrawToStencil;
- pr->drawPath(*clipPath, fill, fGpu, false);
+ pr->drawPath(*clipPath, stroke, fGpu, false);
} else {
- pr->drawPathToStencil(*clipPath, fill, fGpu);
+ pr->stencilPath(*clipPath, stroke, fGpu);
}
}
}
@@ -866,28 +732,22 @@ bool GrClipMaskManager::createStencilClipMask(const GrClipData& clipDataIn,
for (int p = 0; p < passes; ++p) {
*drawState->stencil() = stencilSettings[p];
if (canDrawDirectToClip) {
- if (NULL != clip->fRect) {
+ if (Element::kRect_Type == element->getType()) {
SET_RANDOM_COLOR
- fGpu->drawSimpleRect(*clip->fRect, NULL);
+ fGpu->drawSimpleRect(element->getRect(), NULL);
} else {
+ GrAssert(Element::kPath_Type == element->getType());
SET_RANDOM_COLOR
- pr->drawPath(*clipPath, fill, fGpu, false);
+ pr->drawPath(*clipPath, stroke, fGpu, false);
}
} else {
SET_RANDOM_COLOR
- // 'devClipBounds' is already in device coordinates so the
- // translation in the view matrix is inappropriate.
- // Convert it to canvas space so the drawn rect will
- // be in the correct location
- GrRect canvClipBounds;
- canvClipBounds.set(devClipBounds);
- device_to_canvas(&canvClipBounds, clipDataIn.fOrigin);
- fGpu->drawSimpleRect(canvClipBounds, NULL);
+ // The view matrix is setup to do clip space -> stencil space translation, so
+ // draw rect in clip space.
+ fGpu->drawSimpleRect(SkRect::MakeFromIRect(clipSpaceIBounds), NULL);
}
}
}
- // restore clip
- fGpu->setClip(oldClipData);
}
// set this last because recursive draws may overwrite it back to kNone.
GrAssert(kNone_ClipMaskType == fCurrClipMaskType);
@@ -993,8 +853,7 @@ void GrClipMaskManager::setGpuStencil() {
stencilBits = stencilBuffer->bits();
}
- GrAssert(fGpu->getCaps().stencilWrapOpsSupport() ||
- !settings.usesWrapOp());
+ GrAssert(fGpu->getCaps().stencilWrapOpsSupport() || !settings.usesWrapOp());
GrAssert(fGpu->getCaps().twoSidedStencilSupport() || !settings.isTwoSided());
this->adjustStencilParams(&settings, clipMode, stencilBits);
fGpu->setStencilSettings(settings);
@@ -1082,104 +941,69 @@ void GrClipMaskManager::adjustStencilParams(GrStencilSettings* settings,
}
////////////////////////////////////////////////////////////////////////////////
-
-namespace {
-
-GrPathFill invert_fill(GrPathFill fill) {
- static const GrPathFill gInvertedFillTable[] = {
- kInverseWinding_GrPathFill, // kWinding_GrPathFill
- kInverseEvenOdd_GrPathFill, // kEvenOdd_GrPathFill
- kWinding_GrPathFill, // kInverseWinding_GrPathFill
- kEvenOdd_GrPathFill, // kInverseEvenOdd_GrPathFill
- kHairLine_GrPathFill, // kHairLine_GrPathFill
- };
- GR_STATIC_ASSERT(0 == kWinding_GrPathFill);
- GR_STATIC_ASSERT(1 == kEvenOdd_GrPathFill);
- GR_STATIC_ASSERT(2 == kInverseWinding_GrPathFill);
- GR_STATIC_ASSERT(3 == kInverseEvenOdd_GrPathFill);
- GR_STATIC_ASSERT(4 == kHairLine_GrPathFill);
- GR_STATIC_ASSERT(5 == kGrPathFillCount);
- return gInvertedFillTable[fill];
-}
-
-}
-
-bool GrClipMaskManager::createSoftwareClipMask(const GrClipData& clipDataIn,
- GrTexture** result,
- GrIRect* devResultBounds) {
+GrTexture* GrClipMaskManager::createSoftwareClipMask(int32_t clipStackGenID,
+ GrReducedClip::InitialState initialState,
+ const GrReducedClip::ElementList& elements,
+ const SkIRect& clipSpaceIBounds) {
GrAssert(kNone_ClipMaskType == fCurrClipMaskType);
- if (this->clipMaskPreamble(clipDataIn, result, devResultBounds)) {
- return true;
+ GrTexture* result;
+ if (this->getMaskTexture(clipStackGenID, clipSpaceIBounds, &result)) {
+ return result;
}
- GrTexture* accum = fAACache.getLastMask();
- if (NULL == accum) {
+ if (NULL == result) {
fAACache.reset();
- return false;
+ return NULL;
}
- GrSWMaskHelper helper(this->getContext());
+ // The mask texture may be larger than necessary. We round out the clip space bounds and pin
+ // the top left corner of the resulting rect to the top left of the texture.
+ SkIRect maskSpaceIBounds = SkIRect::MakeWH(clipSpaceIBounds.width(), clipSpaceIBounds.height());
- GrMatrix matrix;
- matrix.setTranslate(SkIntToScalar(-clipDataIn.fOrigin.fX),
- SkIntToScalar(-clipDataIn.fOrigin.fY));
- helper.init(*devResultBounds, &matrix);
+ GrSWMaskHelper helper(this->getContext());
- bool clearToInside;
- SkRegion::Op firstOp = SkRegion::kReplace_Op; // suppress warning
+ SkMatrix matrix;
+ matrix.setTranslate(SkIntToScalar(-clipSpaceIBounds.fLeft),
+ SkIntToScalar(-clipSpaceIBounds.fTop));
+ helper.init(maskSpaceIBounds, &matrix);
- SkClipStack::Iter iter(*clipDataIn.fClipStack,
- SkClipStack::Iter::kBottom_IterStart);
- const SkClipStack::Iter::Clip* clip = process_initial_clip_elements(&iter,
- *devResultBounds,
- &clearToInside,
- &firstOp,
- clipDataIn);
+ helper.clear(kAllIn_InitialState == initialState ? 0xFF : 0x00);
- helper.clear(clearToInside ? 0xFF : 0x00);
+ SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
- bool first = true;
- for ( ; NULL != clip; clip = iter.next()) {
+ for (ElementList::Iter iter(elements.headIter()) ; NULL != iter.get(); iter.next()) {
- SkRegion::Op op = clip->fOp;
- if (first) {
- first = false;
- op = firstOp;
- }
+ const Element* element = iter.get();
+ SkRegion::Op op = element->getOp();
- if (SkRegion::kIntersect_Op == op ||
- SkRegion::kReverseDifference_Op == op) {
- // Intersect and reverse difference require modifying pixels
- // outside of the geometry that is being "drawn". In both cases
- // we erase all the pixels outside of the geometry but
- // leave the pixels inside the geometry alone. For reverse
- // difference we invert all the pixels before clearing the ones
- // outside the geometry.
+ if (SkRegion::kIntersect_Op == op || SkRegion::kReverseDifference_Op == op) {
+ // Intersect and reverse difference require modifying pixels outside of the geometry
+ // that is being "drawn". In both cases we erase all the pixels outside of the geometry
+ // but leave the pixels inside the geometry alone. For reverse difference we invert all
+ // the pixels before clearing the ones outside the geometry.
if (SkRegion::kReverseDifference_Op == op) {
- SkRect temp;
- temp.set(*devResultBounds);
- temp.offset(SkIntToScalar(clipDataIn.fOrigin.fX),
- SkIntToScalar(clipDataIn.fOrigin.fX));
-
+ SkRect temp = SkRect::MakeFromIRect(clipSpaceIBounds);
// invert the entire scene
helper.draw(temp, SkRegion::kXOR_Op, false, 0xFF);
}
- if (NULL != clip->fRect) {
-
+ if (Element::kRect_Type == element->getType()) {
// convert the rect to a path so we can invert the fill
SkPath temp;
- temp.addRect(*clip->fRect);
+ temp.addRect(element->getRect());
+ temp.setFillType(SkPath::kInverseEvenOdd_FillType);
- helper.draw(temp, SkRegion::kReplace_Op,
- kInverseEvenOdd_GrPathFill, clip->fDoAA,
+ helper.draw(temp, stroke, SkRegion::kReplace_Op,
+ element->isAA(),
0x00);
- } else if (NULL != clip->fPath) {
- helper.draw(*clip->fPath,
+ } else {
+ GrAssert(Element::kPath_Type == element->getType());
+ SkPath clipPath = element->getPath();
+ clipPath.toggleInverseFillType();
+ helper.draw(clipPath, stroke,
SkRegion::kReplace_Op,
- invert_fill(get_path_fill(*clip->fPath)),
- clip->fDoAA,
+ element->isAA(),
0x00);
}
@@ -1188,31 +1012,18 @@ bool GrClipMaskManager::createSoftwareClipMask(const GrClipData& clipDataIn,
// The other ops (union, xor, diff) only affect pixels inside
// the geometry so they can just be drawn normally
- if (NULL != clip->fRect) {
-
- helper.draw(*clip->fRect,
- op,
- clip->fDoAA, 0xFF);
-
- } else if (NULL != clip->fPath) {
- helper.draw(*clip->fPath,
- op,
- get_path_fill(*clip->fPath),
- clip->fDoAA, 0xFF);
+ if (Element::kRect_Type == element->getType()) {
+ helper.draw(element->getRect(), op, element->isAA(), 0xFF);
+ } else {
+ GrAssert(Element::kPath_Type == element->getType());
+ helper.draw(element->getPath(), stroke, op, element->isAA(), 0xFF);
}
}
- // Because we are using the scratch texture cache, "accum" may be
- // larger than expected and have some cruft in the areas we aren't using.
- // Clear it out.
- fGpu->clear(NULL, 0x00000000, accum->asRenderTarget());
-
- helper.toTexture(accum, clearToInside ? 0xFF : 0x00);
-
- *result = accum;
+ helper.toTexture(result, kAllIn_InitialState == initialState ? 0xFF : 0x00);
fCurrClipMaskType = kAlpha_ClipMaskType;
- return true;
+ return result;
}
////////////////////////////////////////////////////////////////////////////////
diff --git a/src/gpu/GrClipMaskManager.h b/src/gpu/GrClipMaskManager.h
index d23a525e7a..48d17e1fdc 100644
--- a/src/gpu/GrClipMaskManager.h
+++ b/src/gpu/GrClipMaskManager.h
@@ -12,6 +12,7 @@
#include "GrContext.h"
#include "GrNoncopyable.h"
#include "GrRect.h"
+#include "GrReducedClip.h"
#include "GrStencil.h"
#include "GrTexture.h"
@@ -19,6 +20,7 @@
#include "SkDeque.h"
#include "SkPath.h"
#include "SkRefCnt.h"
+#include "SkTLList.h"
#include "GrClipMaskCache.h"
@@ -110,28 +112,49 @@ private:
GrClipMaskCache fAACache; // cache for the AA path
- bool createStencilClipMask(const GrClipData& clipDataIn,
- const GrIRect& devClipBounds);
- bool createAlphaClipMask(const GrClipData& clipDataIn,
- GrTexture** result,
- GrIRect *devResultBounds);
- bool createSoftwareClipMask(const GrClipData& clipDataIn,
- GrTexture** result,
- GrIRect *devResultBounds);
- bool clipMaskPreamble(const GrClipData& clipDataIn,
- GrTexture** result,
- GrIRect *devResultBounds);
-
- bool useSWOnlyPath(const SkClipStack& clipIn);
-
- bool drawClipShape(GrTexture* target,
- const SkClipStack::Iter::Clip* clip,
- const GrIRect& resultBounds);
-
- void drawTexture(GrTexture* target,
- GrTexture* texture);
-
- void getTemp(const GrIRect& bounds, GrAutoScratchTexture* temp);
+ // Draws the clip into the stencil buffer
+ bool createStencilClipMask(GrReducedClip::InitialState initialState,
+ const GrReducedClip::ElementList& elements,
+ const SkIRect& clipSpaceIBounds,
+ const SkIPoint& clipSpaceToStencilOffset);
+ // Creates an alpha mask of the clip. The mask is a rasterization of elements through the
+ // rect specified by clipSpaceIBounds.
+ GrTexture* createAlphaClipMask(int32_t clipStackGenID,
+ GrReducedClip::InitialState initialState,
+ const GrReducedClip::ElementList& elements,
+ const SkIRect& clipSpaceIBounds);
+ // Similar to createAlphaClipMask but it rasterizes in SW and uploads to the result texture.
+ GrTexture* createSoftwareClipMask(int32_t clipStackGenID,
+ GrReducedClip::InitialState initialState,
+ const GrReducedClip::ElementList& elements,
+ const SkIRect& clipSpaceIBounds);
+
+ // Gets a texture to use for the clip mask. If true is returned then a cached mask was found
+ // that already contains the rasterization of the clip stack, otherwise an uninitialized texture
+ // is returned.
+ bool getMaskTexture(int32_t clipStackGenID,
+ const SkIRect& clipSpaceIBounds,
+ GrTexture** result);
+
+ bool useSWOnlyPath(const GrReducedClip::ElementList& elements);
+
+ // Draws a clip element into the target alpha mask. The caller should have already setup the
+ // desired blend operation. Optionally if the caller already selected a path renderer it can
+ // be passed. Otherwise the function will select one if the element is a path.
+ bool drawElement(GrTexture* target, const SkClipStack::Element*, GrPathRenderer* = NULL);
+
+ // Determines whether it is possible to draw the element to both the stencil buffer and the
+ // alpha mask simultaneously. If so and the element is a path a compatible path renderer is
+ // also returned.
+ bool canStencilAndDrawElement(GrTexture* target, const SkClipStack::Element*, GrPathRenderer**);
+
+ void mergeMask(GrTexture* dstMask,
+ GrTexture* srcMask,
+ SkRegion::Op op,
+ const GrIRect& dstBound,
+ const GrIRect& srcBound);
+
+ void getTemp(int width, int height, GrAutoScratchTexture* temp);
void setupCache(const SkClipStack& clip,
const GrIRect& bounds);
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index 8b7d2d0285..d00e062fae 100644
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -23,6 +23,7 @@
#include "GrSoftwarePathRenderer.h"
#include "GrStencilBuffer.h"
#include "GrTextStrike.h"
+#include "SkStrokeRec.h"
#include "SkTLazy.h"
#include "SkTLS.h"
#include "SkTrace.h"
@@ -48,8 +49,8 @@ SK_DEFINE_INST_COUNT(GrDrawState)
#define GR_DEBUG_PARTIAL_COVERAGE_CHECK 0
#endif
-static const size_t MAX_TEXTURE_CACHE_COUNT = 256;
-static const size_t MAX_TEXTURE_CACHE_BYTES = 16 * 1024 * 1024;
+static const size_t MAX_TEXTURE_CACHE_COUNT = 2048;
+static const size_t MAX_TEXTURE_CACHE_BYTES = GR_DEFAULT_TEXTURE_CACHE_MB_LIMIT * 1024 * 1024;
static const size_t DRAW_BUFFER_VBPOOL_BUFFER_SIZE = 1 << 15;
static const int DRAW_BUFFER_VBPOOL_PREALLOC_BUFFERS = 4;
@@ -59,10 +60,9 @@ static const int DRAW_BUFFER_IBPOOL_PREALLOC_BUFFERS = 4;
#define ASSERT_OWNED_RESOURCE(R) GrAssert(!(R) || (R)->getContext() == this)
-GrContext* GrContext::Create(GrEngine engine,
- GrPlatform3DContext context3D) {
+GrContext* GrContext::Create(GrBackend backend, GrBackendContext context) {
GrContext* ctx = NULL;
- GrGpu* fGpu = GrGpu::Create(engine, context3D);
+ GrGpu* fGpu = GrGpu::Create(backend, context);
if (NULL != fGpu) {
ctx = SkNEW_ARGS(GrContext, (fGpu));
fGpu->unref();
@@ -200,22 +200,15 @@ void convolve_gaussian(GrDrawTarget* target,
GrDrawTarget::AutoStateRestore asr(target, GrDrawTarget::kReset_ASRInit);
GrDrawState* drawState = target->drawState();
drawState->setRenderTarget(rt);
- GrMatrix sampleM;
- sampleM.setIDiv(texture->width(), texture->height());
SkAutoTUnref<GrConvolutionEffect> conv(SkNEW_ARGS(GrConvolutionEffect,
(texture, direction, radius,
sigma)));
- drawState->sampler(0)->setCustomStage(conv, sampleM);
+ drawState->stage(0)->setEffect(conv);
target->drawSimpleRect(rect, NULL);
}
}
-
-GrTexture* GrContext::findTexture(const GrCacheKey& key) {
- return static_cast<GrTexture*>(fTextureCache->find(key.key()));
-}
-
GrTexture* GrContext::findTexture(const GrTextureDesc& desc,
const GrCacheData& cacheData,
const GrTextureParams* params) {
@@ -237,7 +230,7 @@ void GrContext::addStencilBuffer(GrStencilBuffer* sb) {
GrResourceKey resourceKey = GrStencilBuffer::ComputeKey(sb->width(),
sb->height(),
sb->numSamples());
- fTextureCache->create(resourceKey, sb);
+ fTextureCache->addResource(resourceKey, sb);
}
GrStencilBuffer* GrContext::findStencilBuffer(int width, int height,
@@ -287,7 +280,6 @@ GrTexture* GrContext::createResizedTexture(const GrTextureDesc& desc,
if (NULL == clampedTexture) {
clampedTexture = this->createTexture(NULL, desc, cacheData, srcData, rowBytes);
- GrAssert(NULL != clampedTexture);
if (NULL == clampedTexture) {
return NULL;
}
@@ -313,7 +305,7 @@ GrTexture* GrContext::createResizedTexture(const GrTextureDesc& desc,
// texels in the resampled image are copies of texels from
// the original.
GrTextureParams params(SkShader::kClamp_TileMode, needsFiltering);
- drawState->createTextureEffect(0, clampedTexture, GrMatrix::I(), params);
+ drawState->createTextureEffect(0, clampedTexture, SkMatrix::I(), params);
static const GrVertexLayout layout =
GrDrawTarget::StageTexCoordVertexLayoutBit(0,0);
@@ -332,7 +324,7 @@ GrTexture* GrContext::createResizedTexture(const GrTextureDesc& desc,
texture->releaseRenderTarget();
} else {
// TODO: Our CPU stretch doesn't filter. But we create separate
- // stretched textures when the sampler state is either filtered or
+ // stretched textures when the texture params is either filtered or
// not. Either implement filtered stretch blit on CPU or just create
// one when FBO case fails.
@@ -341,17 +333,13 @@ GrTexture* GrContext::createResizedTexture(const GrTextureDesc& desc,
rtDesc.fWidth = GrNextPow2(desc.fWidth);
rtDesc.fHeight = GrNextPow2(desc.fHeight);
int bpp = GrBytesPerPixel(desc.fConfig);
- SkAutoSMalloc<128*128*4> stretchedPixels(bpp *
- rtDesc.fWidth *
- rtDesc.fHeight);
+ SkAutoSMalloc<128*128*4> stretchedPixels(bpp * rtDesc.fWidth * rtDesc.fHeight);
stretchImage(stretchedPixels.get(), rtDesc.fWidth, rtDesc.fHeight,
- srcData, desc.fWidth, desc.fHeight, bpp);
+ srcData, desc.fWidth, desc.fHeight, bpp);
size_t stretchedRowBytes = rtDesc.fWidth * bpp;
- GrTexture* texture = fGpu->createTexture(rtDesc,
- stretchedPixels.get(),
- stretchedRowBytes);
+ GrTexture* texture = fGpu->createTexture(rtDesc, stretchedPixels.get(), stretchedRowBytes);
GrAssert(NULL != texture);
}
@@ -383,7 +371,7 @@ GrTexture* GrContext::createTexture(
}
if (NULL != texture) {
- fTextureCache->create(resourceKey, texture);
+ fTextureCache->addResource(resourceKey, texture);
}
return texture;
@@ -412,7 +400,8 @@ GrTexture* GrContext::lockScratchTexture(const GrTextureDesc& inDesc,
do {
GrResourceKey key = GrTexture::ComputeKey(fGpu, NULL, desc, cacheData, true);
- resource = fTextureCache->find(key);
+ // Ensure we have exclusive access to the texture so future 'find' calls don't return it
+ resource = fTextureCache->find(key, GrResourceCache::kHide_OwnershipFlag);
// if we miss, relax the fit of the flags...
// then try doubling width... then height.
if (NULL != resource || kExact_ScratchTexMatch == match) {
@@ -448,18 +437,12 @@ GrTexture* GrContext::lockScratchTexture(const GrTextureDesc& inDesc,
texture->desc(),
cacheData,
true);
- fTextureCache->create(key, texture);
+ // Make the resource exclusive so future 'find' calls don't return it
+ fTextureCache->addResource(key, texture, GrResourceCache::kHide_OwnershipFlag);
resource = texture;
}
}
- // If the caller gives us the same desc/sampler twice we don't want
- // to return the same texture the second time (unless it was previously
- // released). So make it exclusive to hide it from future searches.
- if (NULL != resource) {
- fTextureCache->makeExclusive(resource->getCacheEntry());
- }
-
return static_cast<GrTexture*>(resource);
}
@@ -531,12 +514,12 @@ int GrContext::getMaxRenderTargetSize() const {
///////////////////////////////////////////////////////////////////////////////
-GrTexture* GrContext::createPlatformTexture(const GrPlatformTextureDesc& desc) {
- return fGpu->createPlatformTexture(desc);
+GrTexture* GrContext::wrapBackendTexture(const GrBackendTextureDesc& desc) {
+ return fGpu->wrapBackendTexture(desc);
}
-GrRenderTarget* GrContext::createPlatformRenderTarget(const GrPlatformRenderTargetDesc& desc) {
- return fGpu->createPlatformRenderTarget(desc);
+GrRenderTarget* GrContext::wrapBackendRenderTarget(const GrBackendRenderTargetDesc& desc) {
+ return fGpu->wrapBackendRenderTarget(desc);
}
///////////////////////////////////////////////////////////////////////////////
@@ -569,7 +552,7 @@ void GrContext::setClip(const GrClipData* clipData) {
fGpu->setClip(clipData);
fDrawState->setState(GrDrawState::kClip_StateBit,
- clipData->fClipStack && !clipData->fClipStack->isWideOpen());
+ clipData && clipData->fClipStack && !clipData->fClipStack->isWideOpen());
}
////////////////////////////////////////////////////////////////////////////////
@@ -585,9 +568,9 @@ void GrContext::drawPaint(const GrPaint& origPaint) {
// don't overflow fixed-point implementations
GrRect r;
r.setLTRB(0, 0,
- GrIntToScalar(getRenderTarget()->width()),
- GrIntToScalar(getRenderTarget()->height()));
- GrMatrix inverse;
+ SkIntToScalar(getRenderTarget()->width()),
+ SkIntToScalar(getRenderTarget()->height()));
+ SkMatrix inverse;
SkTCopyOnFirstWrite<GrPaint> paint(origPaint);
AutoMatrix am;
@@ -629,8 +612,8 @@ inline bool disable_coverage_aa_for_blend(GrDrawTarget* target) {
would be faster.
*/
static void setStrokeRectStrip(GrPoint verts[10], GrRect rect,
- GrScalar width) {
- const GrScalar rad = GrScalarHalf(width);
+ SkScalar width) {
+ const SkScalar rad = SkScalarHalf(width);
rect.sort();
verts[0].set(rect.fLeft + rad, rect.fTop + rad);
@@ -649,15 +632,15 @@ static void setStrokeRectStrip(GrPoint verts[10], GrRect rect,
* Returns true if the rects edges are integer-aligned.
*/
static bool isIRect(const GrRect& r) {
- return GrScalarIsInt(r.fLeft) && GrScalarIsInt(r.fTop) &&
- GrScalarIsInt(r.fRight) && GrScalarIsInt(r.fBottom);
+ return SkScalarIsInt(r.fLeft) && SkScalarIsInt(r.fTop) &&
+ SkScalarIsInt(r.fRight) && SkScalarIsInt(r.fBottom);
}
static bool apply_aa_to_rect(GrDrawTarget* target,
const GrRect& rect,
- GrScalar width,
- const GrMatrix* matrix,
- GrMatrix* combinedMatrix,
+ SkScalar width,
+ const SkMatrix* matrix,
+ SkMatrix* combinedMatrix,
GrRect* devRect,
bool* useVertexCoverage) {
// we use a simple coverage ramp to do aa on axis-aligned rects
@@ -715,15 +698,15 @@ static bool apply_aa_to_rect(GrDrawTarget* target,
void GrContext::drawRect(const GrPaint& paint,
const GrRect& rect,
- GrScalar width,
- const GrMatrix* matrix) {
+ SkScalar width,
+ const SkMatrix* matrix) {
SK_TRACE_EVENT0("GrContext::drawRect");
GrDrawTarget* target = this->prepareToDraw(&paint, DEFAULT_BUFFERING);
GrDrawState::AutoStageDisable atr(fDrawState);
GrRect devRect = rect;
- GrMatrix combinedMatrix;
+ SkMatrix combinedMatrix;
bool useVertexCoverage;
bool needAA = paint.isAntiAlias() &&
!this->getRenderTarget()->isMultisampled();
@@ -737,13 +720,13 @@ void GrContext::drawRect(const GrPaint& paint,
return;
}
if (width >= 0) {
- GrVec strokeSize;;
+ GrVec strokeSize;
if (width > 0) {
strokeSize.set(width, width);
combinedMatrix.mapVectors(&strokeSize, 1);
strokeSize.setAbs(strokeSize);
} else {
- strokeSize.set(GR_Scalar1, GR_Scalar1);
+ strokeSize.set(SK_Scalar1, SK_Scalar1);
}
fAARectRenderer->strokeAARect(this->getGpu(), target, devRect,
strokeSize, useVertexCoverage);
@@ -802,10 +785,10 @@ void GrContext::drawRect(const GrPaint& paint,
}
target->setVertexSourceToBuffer(0, sqVB);
GrDrawState* drawState = target->drawState();
- GrMatrix m;
+ SkMatrix m;
m.setAll(rect.width(), 0, rect.fLeft,
0, rect.height(), rect.fTop,
- 0, 0, GrMatrix::I()[8]);
+ 0, 0, SkMatrix::I()[8]);
if (NULL != matrix) {
m.postConcat(*matrix);
@@ -822,8 +805,8 @@ void GrContext::drawRect(const GrPaint& paint,
void GrContext::drawRectToRect(const GrPaint& paint,
const GrRect& dstRect,
const GrRect& srcRect,
- const GrMatrix* dstMatrix,
- const GrMatrix* srcMatrix) {
+ const SkMatrix* dstMatrix,
+ const SkMatrix* srcMatrix) {
SK_TRACE_EVENT0("GrContext::drawRectToRect");
// srcRect refers to paint's first color stage
@@ -838,11 +821,11 @@ void GrContext::drawRectToRect(const GrPaint& paint,
GrDrawState::AutoStageDisable atr(fDrawState);
GrDrawState* drawState = target->drawState();
- GrMatrix m;
+ SkMatrix m;
m.setAll(dstRect.width(), 0, dstRect.fLeft,
0, dstRect.height(), dstRect.fTop,
- 0, 0, GrMatrix::I()[8]);
+ 0, 0, SkMatrix::I()[8]);
if (NULL != dstMatrix) {
m.postConcat(*dstMatrix);
}
@@ -854,11 +837,12 @@ void GrContext::drawRectToRect(const GrPaint& paint,
m.setAll(srcRect.width(), 0, srcRect.fLeft,
0, srcRect.height(), srcRect.fTop,
- 0, 0, GrMatrix::I()[8]);
+ 0, 0, SkMatrix::I()[8]);
if (NULL != srcMatrix) {
m.postConcat(*srcMatrix);
}
- drawState->sampler(GrPaint::kFirstColorStage)->preConcatMatrix(m);
+
+ drawState->stage(GrPaint::kFirstColorStage)->preConcatCoordChange(m);
const GrVertexBuffer* sqVB = fGpu->getUnitSquareVertexBuffer();
if (NULL == sqVB) {
@@ -871,7 +855,7 @@ void GrContext::drawRectToRect(const GrPaint& paint,
GrDrawState::AutoStageDisable atr(fDrawState);
const GrRect* srcRects[GrDrawState::kNumStages] = {NULL};
- const GrMatrix* srcMatrices[GrDrawState::kNumStages] = {NULL};
+ const SkMatrix* srcMatrices[GrDrawState::kNumStages] = {NULL};
srcRects[0] = &srcRect;
srcMatrices[0] = srcMatrix;
@@ -949,8 +933,8 @@ namespace {
struct CircleVertex {
GrPoint fPos;
GrPoint fCenter;
- GrScalar fOuterRadius;
- GrScalar fInnerRadius;
+ SkScalar fOuterRadius;
+ SkScalar fInnerRadius;
};
/* Returns true if will map a circle to another circle. This can be true
@@ -997,9 +981,13 @@ void GrContext::drawOval(const GrPaint& paint,
rect.height() != rect.width()) {
SkPath path;
path.addOval(rect);
- GrPathFill fill = (strokeWidth == 0) ?
- kHairLine_GrPathFill : kWinding_GrPathFill;
- this->internalDrawPath(paint, path, fill);
+ path.setFillType(SkPath::kWinding_FillType);
+ SkStrokeRec stroke(0 == strokeWidth ? SkStrokeRec::kHairline_InitStyle :
+ SkStrokeRec::kFill_InitStyle);
+ if (strokeWidth > 0) {
+ stroke.setStrokeStyle(strokeWidth, true);
+ }
+ this->internalDrawPath(paint, path, stroke);
return;
}
@@ -1007,7 +995,7 @@ void GrContext::drawOval(const GrPaint& paint,
GrDrawState* drawState = target->drawState();
GrDrawState::AutoStageDisable atr(fDrawState);
- const GrMatrix vm = drawState->getViewMatrix();
+ const SkMatrix vm = drawState->getViewMatrix();
const GrRenderTarget* rt = drawState->getRenderTarget();
if (NULL == rt) {
@@ -1023,13 +1011,13 @@ void GrContext::drawOval(const GrPaint& paint,
GrAssert(sizeof(CircleVertex) == GrDrawTarget::VertexSize(layout));
GrPoint center = GrPoint::Make(rect.centerX(), rect.centerY());
- GrScalar radius = SkScalarHalf(rect.width());
+ SkScalar radius = SkScalarHalf(rect.width());
vm.mapPoints(&center, 1);
radius = vm.mapRadius(radius);
- GrScalar outerRadius = radius;
- GrScalar innerRadius = 0;
+ SkScalar outerRadius = radius;
+ SkScalar innerRadius = 0;
SkScalar halfWidth = 0;
if (strokeWidth == 0) {
halfWidth = SkScalarHalf(SK_Scalar1);
@@ -1060,9 +1048,7 @@ void GrContext::drawOval(const GrPaint& paint,
verts[3].fPos = SkPoint::Make(R, B);
for (int i = 0; i < 4; ++i) {
- // this goes to fragment shader, it should be in y-points-up space.
- verts[i].fCenter = SkPoint::Make(center.fX, rt->height() - center.fY);
-
+ verts[i].fCenter = center;
verts[i].fOuterRadius = outerRadius;
verts[i].fInnerRadius = innerRadius;
}
@@ -1071,26 +1057,36 @@ void GrContext::drawOval(const GrPaint& paint,
target->drawNonIndexed(kTriangleStrip_GrPrimitiveType, 0, 4);
}
-void GrContext::drawPath(const GrPaint& paint, const SkPath& path, GrPathFill fill) {
+void GrContext::drawPath(const GrPaint& paint, const SkPath& path, const SkStrokeRec& stroke) {
if (path.isEmpty()) {
- if (GrIsFillInverted(fill)) {
+ if (path.isInverseFillType()) {
this->drawPaint(paint);
}
return;
}
+ const SkPath* pathPtr = &path;
+ SkPath tmpPath;
+ SkStrokeRec strokeRec(stroke);
+ if (!strokeRec.isHairlineStyle()) {
+ if (strokeRec.applyToPath(&tmpPath, *pathPtr)) {
+ pathPtr = &tmpPath;
+ strokeRec.setFillStyle();
+ }
+ }
+
SkRect ovalRect;
- if (!GrIsFillInverted(fill) && path.isOval(&ovalRect)) {
- SkScalar width = (fill == kHairLine_GrPathFill) ? 0 : -SK_Scalar1;
+ if (!pathPtr->isInverseFillType() && pathPtr->isOval(&ovalRect)) {
+ SkScalar width = strokeRec.isHairlineStyle() ? 0 : -SK_Scalar1;
this->drawOval(paint, ovalRect, width);
return;
}
- this->internalDrawPath(paint, path, fill);
+ this->internalDrawPath(paint, *pathPtr, strokeRec);
}
-void GrContext::internalDrawPath(const GrPaint& paint, const SkPath& path, GrPathFill fill) {
+void GrContext::internalDrawPath(const GrPaint& paint, const SkPath& path, const SkStrokeRec& stroke) {
// Note that below we may sw-rasterize the path into a scratch texture.
// Scratch textures can be recycled after they are returned to the texture
@@ -1113,7 +1109,10 @@ void GrContext::internalDrawPath(const GrPaint& paint, const SkPath& path, GrPat
prAA = false;
}
- GrPathRenderer* pr = this->getPathRenderer(path, fill, target, prAA, true);
+ GrPathRendererChain::DrawType type = prAA ? GrPathRendererChain::kColorAntiAlias_DrawType :
+ GrPathRendererChain::kColor_DrawType;
+
+ GrPathRenderer* pr = this->getPathRenderer(path, stroke, target, true, type);
if (NULL == pr) {
#if GR_DEBUG
GrPrintf("Unable to find path renderer compatible with path.\n");
@@ -1121,7 +1120,7 @@ void GrContext::internalDrawPath(const GrPaint& paint, const SkPath& path, GrPat
return;
}
- pr->drawPath(path, fill, target, prAA);
+ pr->drawPath(path, stroke, target, prAA);
}
////////////////////////////////////////////////////////////////////////////////
@@ -1318,36 +1317,45 @@ bool GrContext::readRenderTargetPixels(GrRenderTarget* target,
ast.set(this, desc, match);
GrTexture* texture = ast.texture();
if (texture) {
- SkAutoTUnref<GrCustomStage> stage;
+ GrEffectStage stage;
+ // compute a matrix to perform the draw
+ SkMatrix textureMatrix;
+ if (flipY) {
+ textureMatrix.setTranslate(SK_Scalar1 * left,
+ SK_Scalar1 * (top + height));
+ textureMatrix.set(SkMatrix::kMScaleY, -SK_Scalar1);
+ } else {
+ textureMatrix.setTranslate(SK_Scalar1 *left, SK_Scalar1 *top);
+ }
+ textureMatrix.postIDiv(src->width(), src->height());
+
+ bool effectInstalled = false;
if (unpremul) {
- stage.reset(this->createPMToUPMEffect(src, swapRAndB));
+ if (this->installPMToUPMEffect(src, swapRAndB, textureMatrix, &stage)) {
+ effectInstalled = true;
+ unpremul = false; // we no longer need to do this on CPU after the readback.
+ }
}
// If we failed to create a PM->UPM effect and have no other conversions to perform then
// there is no longer any point to using the scratch.
- if (NULL != stage || flipY || swapRAndB) {
- if (NULL == stage) {
- stage.reset(GrConfigConversionEffect::Create(src, swapRAndB));
- GrAssert(NULL != stage);
- } else {
- unpremul = false; // we will handle the UPM conversion in the draw
+ if (effectInstalled || flipY || swapRAndB) {
+ if (!effectInstalled) {
+ SkAssertResult(GrConfigConversionEffect::InstallEffect(
+ src,
+ swapRAndB,
+ GrConfigConversionEffect::kNone_PMConversion,
+ textureMatrix,
+ &stage));
}
swapRAndB = false; // we will handle the swap in the draw.
+ flipY = false; // we already incorporated the y flip in the matrix
GrDrawTarget::AutoStateRestore asr(fGpu, GrDrawTarget::kReset_ASRInit);
GrDrawState* drawState = fGpu->drawState();
+ *drawState->stage(0) = stage;
+
drawState->setRenderTarget(texture->asRenderTarget());
- GrMatrix matrix;
- if (flipY) {
- matrix.setTranslate(SK_Scalar1 * left,
- SK_Scalar1 * (top + height));
- matrix.set(GrMatrix::kMScaleY, -GR_Scalar1);
- flipY = false; // the y flip will be handled in the draw
- } else {
- matrix.setTranslate(SK_Scalar1 *left, SK_Scalar1 *top);
- }
- matrix.postIDiv(src->width(), src->height());
- drawState->sampler(0)->setCustomStage(stage, matrix);
- GrRect rect = GrRect::MakeWH(GrIntToScalar(width), GrIntToScalar(height));
+ GrRect rect = GrRect::MakeWH(SkIntToScalar(width), SkIntToScalar(height));
fGpu->drawSimpleRect(rect, NULL);
// we want to read back from the scratch's origin
left = 0;
@@ -1361,7 +1369,7 @@ bool GrContext::readRenderTargetPixels(GrRenderTarget* target,
readConfig, buffer, rowBytes, readUpsideDown)) {
return false;
}
- // Perform any conversions we weren't able to perfom using a scratch texture.
+ // Perform any conversions we weren't able to perform using a scratch texture.
if (unpremul || swapRAndB || flipY) {
// These are initialized to suppress a warning
SkCanvas::Config8888 srcC8888 = SkCanvas::kNative_Premul_Config8888;
@@ -1429,7 +1437,7 @@ void GrContext::resolveRenderTarget(GrRenderTarget* target) {
fGpu->resolveRenderTarget(target);
}
-void GrContext::copyTexture(GrTexture* src, GrRenderTarget* dst) {
+void GrContext::copyTexture(GrTexture* src, GrRenderTarget* dst, const SkIPoint* topLeft) {
if (NULL == src || NULL == dst) {
return;
}
@@ -1444,13 +1452,20 @@ void GrContext::copyTexture(GrTexture* src, GrRenderTarget* dst) {
GrDrawTarget::AutoStateRestore asr(fGpu, GrDrawTarget::kReset_ASRInit);
GrDrawState* drawState = fGpu->drawState();
drawState->setRenderTarget(dst);
- GrMatrix sampleM;
+ SkMatrix sampleM;
sampleM.setIDiv(src->width(), src->height());
+ SkIRect srcRect = SkIRect::MakeWH(dst->width(), dst->height());
+ if (NULL != topLeft) {
+ srcRect.offset(*topLeft);
+ }
+ SkIRect srcBounds = SkIRect::MakeWH(src->width(), src->height());
+ if (!srcRect.intersect(srcBounds)) {
+ return;
+ }
+ sampleM.preTranslate(SkIntToScalar(srcRect.fLeft), SkIntToScalar(srcRect.fTop));
drawState->createTextureEffect(0, src, sampleM);
- SkRect rect = SkRect::MakeXYWH(0, 0,
- SK_Scalar1 * src->width(),
- SK_Scalar1 * src->height());
- fGpu->drawSimpleRect(rect, NULL);
+ SkRect dstR = SkRect::MakeWH(SkIntToScalar(srcRect.width()), SkIntToScalar(srcRect.height()));
+ fGpu->drawSimpleRect(dstR, NULL);
}
void GrContext::writeRenderTargetPixels(GrRenderTarget* target,
@@ -1491,7 +1506,7 @@ void GrContext::writeRenderTargetPixels(GrRenderTarget* target,
return;
}
#endif
- SkAutoTUnref<GrCustomStage> stage;
+
bool swapRAndB = (fGpu->preferredReadPixelsConfig(config) == GrPixelConfigSwapRAndB(config));
GrPixelConfig textureConfig;
@@ -1510,15 +1525,24 @@ void GrContext::writeRenderTargetPixels(GrRenderTarget* target,
if (NULL == texture) {
return;
}
+
+ GrEffectStage stage;
+ SkMatrix textureMatrix;
+ textureMatrix.setIDiv(texture->width(), texture->height());
+
// allocate a tmp buffer and sw convert the pixels to premul
SkAutoSTMalloc<128 * 128, uint32_t> tmpPixels(0);
+ bool effectInstalled = false;
if (kUnpremul_PixelOpsFlag & flags) {
if (kRGBA_8888_GrPixelConfig != config && kBGRA_8888_GrPixelConfig != config) {
return;
}
- stage.reset(this->createUPMToPMEffect(texture, swapRAndB));
- if (NULL == stage) {
+ effectInstalled = this->installUPMToPMEffect(texture,
+ swapRAndB,
+ textureMatrix,
+ &stage);
+ if (!effectInstalled) {
SkCanvas::Config8888 srcConfig8888, dstConfig8888;
GR_DEBUGCODE(bool success = )
grconfig_to_config8888(config, true, &srcConfig8888);
@@ -1535,9 +1559,13 @@ void GrContext::writeRenderTargetPixels(GrRenderTarget* target,
rowBytes = 4 * width;
}
}
- if (NULL == stage) {
- stage.reset(GrConfigConversionEffect::Create(texture, swapRAndB));
- GrAssert(NULL != stage);
+ if (!effectInstalled) {
+ SkAssertResult(GrConfigConversionEffect::InstallEffect(
+ texture,
+ swapRAndB,
+ GrConfigConversionEffect::kNone_PMConversion,
+ textureMatrix,
+ &stage));
}
this->writeTexturePixels(texture,
@@ -1547,15 +1575,13 @@ void GrContext::writeRenderTargetPixels(GrRenderTarget* target,
GrDrawTarget::AutoStateRestore asr(fGpu, GrDrawTarget::kReset_ASRInit);
GrDrawState* drawState = fGpu->drawState();
+ *drawState->stage(0) = stage;
- GrMatrix matrix;
- matrix.setTranslate(GrIntToScalar(left), GrIntToScalar(top));
+ SkMatrix matrix;
+ matrix.setTranslate(SkIntToScalar(left), SkIntToScalar(top));
drawState->setViewMatrix(matrix);
drawState->setRenderTarget(target);
- matrix.setIDiv(texture->width(), texture->height());
- drawState->sampler(0)->setCustomStage(stage, matrix);
-
fGpu->drawSimpleRect(GrRect::MakeWH(SkIntToScalar(width), SkIntToScalar(height)), NULL);
}
////////////////////////////////////////////////////////////////////////////////
@@ -1592,25 +1618,26 @@ GrDrawTarget* GrContext::prepareToDraw(const GrPaint* paint, BufferedDraw buffer
* can be individually allowed/disallowed via the "allowSW" boolean.
*/
GrPathRenderer* GrContext::getPathRenderer(const SkPath& path,
- GrPathFill fill,
+ const SkStrokeRec& stroke,
const GrDrawTarget* target,
- bool antiAlias,
- bool allowSW) {
+ bool allowSW,
+ GrPathRendererChain::DrawType drawType,
+ GrPathRendererChain::StencilSupport* stencilSupport) {
+
if (NULL == fPathRendererChain) {
- fPathRendererChain =
- SkNEW_ARGS(GrPathRendererChain,
- (this, GrPathRendererChain::kNone_UsageFlag));
+ fPathRendererChain = SkNEW_ARGS(GrPathRendererChain, (this));
}
- GrPathRenderer* pr = fPathRendererChain->getPathRenderer(path, fill,
+ GrPathRenderer* pr = fPathRendererChain->getPathRenderer(path,
+ stroke,
target,
- antiAlias);
+ drawType,
+ stencilSupport);
if (NULL == pr && allowSW) {
if (NULL == fSoftwarePathRenderer) {
fSoftwarePathRenderer = SkNEW_ARGS(GrSoftwarePathRenderer, (this));
}
-
pr = fSoftwarePathRenderer;
}
@@ -1636,11 +1663,11 @@ bool GrContext::isConfigRenderable(GrPixelConfig config) const {
return fGpu->isConfigRenderable(config);
}
-const GrMatrix& GrContext::getMatrix() const {
+const SkMatrix& GrContext::getMatrix() const {
return fDrawState->getViewMatrix();
}
-void GrContext::setMatrix(const GrMatrix& m) {
+void GrContext::setMatrix(const SkMatrix& m) {
fDrawState->setViewMatrix(m);
}
@@ -1648,7 +1675,7 @@ void GrContext::setIdentityMatrix() {
fDrawState->viewMatrix()->reset();
}
-void GrContext::concatMatrix(const GrMatrix& m) const {
+void GrContext::concatMatrix(const SkMatrix& m) const {
fDrawState->preConcatViewMatrix(m);
}
@@ -1737,7 +1764,10 @@ void test_pm_conversions(GrContext* ctx, int* pmToUPMValue, int* upmToPMValue) {
}
}
-GrCustomStage* GrContext::createPMToUPMEffect(GrTexture* texture, bool swapRAndB) {
+bool GrContext::installPMToUPMEffect(GrTexture* texture,
+ bool swapRAndB,
+ const SkMatrix& matrix,
+ GrEffectStage* stage) {
if (!fDidTestPMConversions) {
test_pm_conversions(this, &fPMToUPMConversion, &fUPMToPMConversion);
fDidTestPMConversions = true;
@@ -1745,13 +1775,17 @@ GrCustomStage* GrContext::createPMToUPMEffect(GrTexture* texture, bool swapRAndB
GrConfigConversionEffect::PMConversion pmToUPM =
static_cast<GrConfigConversionEffect::PMConversion>(fPMToUPMConversion);
if (GrConfigConversionEffect::kNone_PMConversion != pmToUPM) {
- return GrConfigConversionEffect::Create(texture, swapRAndB, pmToUPM);
+ GrConfigConversionEffect::InstallEffect(texture, swapRAndB, pmToUPM, matrix, stage);
+ return true;
} else {
- return NULL;
+ return false;
}
}
-GrCustomStage* GrContext::createUPMToPMEffect(GrTexture* texture, bool swapRAndB) {
+bool GrContext::installUPMToPMEffect(GrTexture* texture,
+ bool swapRAndB,
+ const SkMatrix& matrix,
+ GrEffectStage* stage) {
if (!fDidTestPMConversions) {
test_pm_conversions(this, &fPMToUPMConversion, &fUPMToPMConversion);
fDidTestPMConversions = true;
@@ -1759,9 +1793,10 @@ GrCustomStage* GrContext::createUPMToPMEffect(GrTexture* texture, bool swapRAndB
GrConfigConversionEffect::PMConversion upmToPM =
static_cast<GrConfigConversionEffect::PMConversion>(fUPMToPMConversion);
if (GrConfigConversionEffect::kNone_PMConversion != upmToPM) {
- return GrConfigConversionEffect::Create(texture, swapRAndB, upmToPM);
+ GrConfigConversionEffect::InstallEffect(texture, swapRAndB, upmToPM, matrix, stage);
+ return true;
} else {
- return NULL;
+ return false;
}
}
@@ -1803,20 +1838,23 @@ GrTexture* GrContext::gaussianBlur(GrTexture* srcTexture,
GrAutoScratchTexture temp1, temp2;
GrTexture* dstTexture = temp1.set(this, desc);
GrTexture* tempTexture = canClobberSrc ? srcTexture : temp2.set(this, desc);
+ if (NULL == dstTexture || NULL == tempTexture) {
+ return NULL;
+ }
GrPaint paint;
paint.reset();
for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) {
- GrMatrix matrix;
+ SkMatrix matrix;
matrix.setIDiv(srcTexture->width(), srcTexture->height());
this->setRenderTarget(dstTexture->asRenderTarget());
SkRect dstRect(srcRect);
scale_rect(&dstRect, i < scaleFactorX ? 0.5f : 1.0f,
i < scaleFactorY ? 0.5f : 1.0f);
- paint.colorSampler(0)->setCustomStage(SkNEW_ARGS(GrSingleTextureEffect,
- (srcTexture, true)), matrix)->unref();
+ paint.colorStage(0)->setEffect(SkNEW_ARGS(GrSingleTextureEffect,
+ (srcTexture, matrix, true)))->unref();
this->drawRectToRect(paint, dstRect, srcRect);
srcRect = dstRect;
srcTexture = dstTexture;
@@ -1869,13 +1907,12 @@ GrTexture* GrContext::gaussianBlur(GrTexture* srcTexture,
clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop,
1, srcIRect.height());
this->clear(&clearRect, 0x0);
- GrMatrix matrix;
+ SkMatrix matrix;
// FIXME: This should be mitchell, not bilinear.
matrix.setIDiv(srcTexture->width(), srcTexture->height());
this->setRenderTarget(dstTexture->asRenderTarget());
- paint.colorSampler(0)->setCustomStage(SkNEW_ARGS(GrSingleTextureEffect,
- (srcTexture, true)),
- matrix)->unref();
+ paint.colorStage(0)->setEffect(SkNEW_ARGS(GrSingleTextureEffect,(srcTexture,
+ matrix, true)))->unref();
SkRect dstRect(srcRect);
scale_rect(&dstRect, (float) scaleFactorX, (float) scaleFactorY);
this->drawRectToRect(paint, dstRect, srcRect);
diff --git a/src/gpu/GrCustomStage.cpp b/src/gpu/GrCustomStage.cpp
deleted file mode 100644
index 0000278955..0000000000
--- a/src/gpu/GrCustomStage.cpp
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright 2012 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "GrContext.h"
-#include "GrCustomStage.h"
-#include "GrMemoryPool.h"
-#include "SkTLS.h"
-
-SK_DEFINE_INST_COUNT(GrCustomStage)
-
-#if SK_ALLOW_STATIC_GLOBAL_INITIALIZERS
-SkTArray<GrCustomStageTestFactory*, true>* GrCustomStageTestFactory::GetFactories() {
- static SkTArray<GrCustomStageTestFactory*, true> gFactories;
- return &gFactories;
-}
-#endif
-
-class GrCustomStage_Globals {
-public:
- static GrMemoryPool* GetTLS() {
- return (GrMemoryPool*)SkTLS::Get(CreateTLS, DeleteTLS);
- }
-
-private:
- static void* CreateTLS() {
- return SkNEW_ARGS(GrMemoryPool, (4096, 4096));
- }
-
- static void DeleteTLS(void* pool) {
- SkDELETE(reinterpret_cast<GrMemoryPool*>(pool));
- }
-};
-
-int32_t GrProgramStageFactory::fCurrStageClassID =
- GrProgramStageFactory::kIllegalStageClassID;
-
-GrCustomStage::GrCustomStage(int numTextures)
- : fNumTextures(numTextures) {
-}
-
-GrCustomStage::~GrCustomStage() {
-
-}
-
-bool GrCustomStage::isOpaque(bool inputTextureIsOpaque) const {
- return false;
-}
-
-bool GrCustomStage::isEqual(const GrCustomStage& s) const {
- if (this->numTextures() != s.numTextures()) {
- return false;
- }
- for (int i = 0; i < this->numTextures(); ++i) {
- if (this->textureAccess(i) != s.textureAccess(i)) {
- return false;
- }
- }
- return true;
-}
-
-const GrTextureAccess& GrCustomStage::textureAccess(int index) const {
- GrCrash("We shouldn't be calling this function on the base class.");
- static GrTextureAccess kDummy;
- return kDummy;
-}
-
-void * GrCustomStage::operator new(size_t size) {
- return GrCustomStage_Globals::GetTLS()->allocate(size);
-}
-
-void GrCustomStage::operator delete(void* target) {
- GrCustomStage_Globals::GetTLS()->release(target);
-}
diff --git a/src/gpu/GrDefaultPathRenderer.cpp b/src/gpu/GrDefaultPathRenderer.cpp
index 4f725af4f1..dbed7839f8 100644
--- a/src/gpu/GrDefaultPathRenderer.cpp
+++ b/src/gpu/GrDefaultPathRenderer.cpp
@@ -12,6 +12,7 @@
#include "GrDrawState.h"
#include "GrPathUtils.h"
#include "SkString.h"
+#include "SkStrokeRec.h"
#include "SkTrace.h"
@@ -150,31 +151,36 @@ GR_STATIC_CONST_SAME_STENCIL(gDirectToStencil,
#define STENCIL_OFF 0 // Always disable stencil (even when needed)
-static inline bool single_pass_path(const SkPath& path, GrPathFill fill) {
+static inline bool single_pass_path(const SkPath& path, const SkStrokeRec& stroke) {
#if STENCIL_OFF
return true;
#else
- if (kEvenOdd_GrPathFill == fill || kWinding_GrPathFill == fill) {
+ if (!stroke.isHairlineStyle() && !path.isInverseFillType()) {
return path.isConvex();
}
return false;
#endif
}
-bool GrDefaultPathRenderer::requiresStencilPass(const SkPath& path,
- GrPathFill fill,
- const GrDrawTarget* target) const {
- return !single_pass_path(path, fill);
+GrPathRenderer::StencilSupport GrDefaultPathRenderer::onGetStencilSupport(
+ const SkPath& path,
+ const SkStrokeRec& stroke,
+ const GrDrawTarget*) const {
+ if (single_pass_path(path, stroke)) {
+ return GrPathRenderer::kNoRestriction_StencilSupport;
+ } else {
+ return GrPathRenderer::kStencilOnly_StencilSupport;
+ }
}
-static inline void append_countour_edge_indices(GrPathFill fillType,
+static inline void append_countour_edge_indices(bool hairLine,
uint16_t fanCenterIdx,
uint16_t edgeV0Idx,
uint16_t** indices) {
// when drawing lines we're appending line segments along
// the contour. When applying the other fill rules we're
// drawing triangle fans around fanCenterIdx.
- if (kHairLine_GrPathFill != fillType) {
+ if (!hairLine) {
*((*indices)++) = fanCenterIdx;
}
*((*indices)++) = edgeV0Idx;
@@ -182,8 +188,8 @@ static inline void append_countour_edge_indices(GrPathFill fillType,
}
bool GrDefaultPathRenderer::createGeom(const SkPath& path,
- GrPathFill fill,
- GrScalar srcSpaceTol,
+ const SkStrokeRec& stroke,
+ SkScalar srcSpaceTol,
GrDrawTarget* target,
GrPrimitiveType* primType,
int* vertexCnt,
@@ -192,7 +198,7 @@ bool GrDefaultPathRenderer::createGeom(const SkPath& path,
{
SK_TRACE_EVENT0("GrDefaultPathRenderer::createGeom");
- GrScalar srcSpaceTolSqd = GrMul(srcSpaceTol, srcSpaceTol);
+ SkScalar srcSpaceTolSqd = SkScalarMul(srcSpaceTol, srcSpaceTol);
int contourCnt;
int maxPts = GrPathUtils::worstCasePointCount(path, &contourCnt,
srcSpaceTol);
@@ -208,8 +214,10 @@ bool GrDefaultPathRenderer::createGeom(const SkPath& path,
GrVertexLayout layout = 0;
bool indexed = contourCnt > 1;
+ const bool isHairline = stroke.isHairlineStyle();
+
int maxIdxs = 0;
- if (kHairLine_GrPathFill == fill) {
+ if (isHairline) {
if (indexed) {
maxIdxs = 2 * maxPts;
*primType = kLines_GrPrimitiveType;
@@ -230,7 +238,7 @@ bool GrDefaultPathRenderer::createGeom(const SkPath& path,
return false;
}
- uint16_t* idxBase = reinterpret_cast<uint16_t*>(arg->indices());;
+ uint16_t* idxBase = reinterpret_cast<uint16_t*>(arg->indices());
uint16_t* idx = idxBase;
uint16_t subpathIdxStart = 0;
@@ -260,7 +268,7 @@ bool GrDefaultPathRenderer::createGeom(const SkPath& path,
case kLine_PathCmd:
if (indexed) {
uint16_t prevIdx = (uint16_t)(vert - base) - 1;
- append_countour_edge_indices(fill, subpathIdxStart,
+ append_countour_edge_indices(isHairline, subpathIdxStart,
prevIdx, &idx);
}
*(vert++) = pts[1];
@@ -275,7 +283,7 @@ bool GrDefaultPathRenderer::createGeom(const SkPath& path,
GrPathUtils::quadraticPointCount(pts, srcSpaceTol));
if (indexed) {
for (uint16_t i = 0; i < numPts; ++i) {
- append_countour_edge_indices(fill, subpathIdxStart,
+ append_countour_edge_indices(isHairline, subpathIdxStart,
firstQPtIdx + i, &idx);
}
}
@@ -290,7 +298,7 @@ bool GrDefaultPathRenderer::createGeom(const SkPath& path,
GrPathUtils::cubicPointCount(pts, srcSpaceTol));
if (indexed) {
for (uint16_t i = 0; i < numPts; ++i) {
- append_countour_edge_indices(fill, subpathIdxStart,
+ append_countour_edge_indices(isHairline, subpathIdxStart,
firstCPtIdx + i, &idx);
}
}
@@ -316,12 +324,12 @@ FINISHED:
}
bool GrDefaultPathRenderer::internalDrawPath(const SkPath& path,
- GrPathFill fill,
+ const SkStrokeRec& stroke,
GrDrawTarget* target,
bool stencilOnly) {
- GrMatrix viewM = target->getDrawState().getViewMatrix();
- GrScalar tol = GR_Scalar1;
+ SkMatrix viewM = target->getDrawState().getViewMatrix();
+ SkScalar tol = SK_Scalar1;
tol = GrPathUtils::scaleToleranceToSrc(tol, viewM, path.getBounds());
int vertexCnt;
@@ -329,7 +337,7 @@ bool GrDefaultPathRenderer::internalDrawPath(const SkPath& path,
GrPrimitiveType primType;
GrDrawTarget::AutoReleaseGeometry arg;
if (!this->createGeom(path,
- fill,
+ stroke,
tol,
target,
&primType,
@@ -352,7 +360,7 @@ bool GrDefaultPathRenderer::internalDrawPath(const SkPath& path,
bool reverse = false;
bool lastPassIsBounds;
- if (kHairLine_GrPathFill == fill) {
+ if (stroke.isHairlineStyle()) {
passCount = 1;
if (stencilOnly) {
passes[0] = &gDirectToStencil;
@@ -362,7 +370,7 @@ bool GrDefaultPathRenderer::internalDrawPath(const SkPath& path,
lastPassIsBounds = false;
drawFace[0] = GrDrawState::kBoth_DrawFace;
} else {
- if (single_pass_path(path, fill)) {
+ if (single_pass_path(path, stroke)) {
passCount = 1;
if (stencilOnly) {
passes[0] = &gDirectToStencil;
@@ -372,11 +380,11 @@ bool GrDefaultPathRenderer::internalDrawPath(const SkPath& path,
drawFace[0] = GrDrawState::kBoth_DrawFace;
lastPassIsBounds = false;
} else {
- switch (fill) {
- case kInverseEvenOdd_GrPathFill:
+ switch (path.getFillType()) {
+ case SkPath::kInverseEvenOdd_FillType:
reverse = true;
// fallthrough
- case kEvenOdd_GrPathFill:
+ case SkPath::kEvenOdd_FillType:
passes[0] = &gEOStencilPass;
if (stencilOnly) {
passCount = 1;
@@ -393,10 +401,10 @@ bool GrDefaultPathRenderer::internalDrawPath(const SkPath& path,
drawFace[0] = drawFace[1] = GrDrawState::kBoth_DrawFace;
break;
- case kInverseWinding_GrPathFill:
+ case SkPath::kInverseWinding_FillType:
reverse = true;
// fallthrough
- case kWinding_GrPathFill:
+ case SkPath::kWinding_FillType:
if (fSeparateStencil) {
if (fStencilWrapOps) {
passes[0] = &gWindStencilSeparateWithWrap;
@@ -450,24 +458,20 @@ bool GrDefaultPathRenderer::internalDrawPath(const SkPath& path,
drawState->disableState(GrDrawState::kNoColorWrites_StateBit);
}
GrRect bounds;
+ GrDrawState::AutoDeviceCoordDraw adcd;
if (reverse) {
GrAssert(NULL != drawState->getRenderTarget());
// draw over the whole world.
bounds.setLTRB(0, 0,
- GrIntToScalar(drawState->getRenderTarget()->width()),
- GrIntToScalar(drawState->getRenderTarget()->height()));
- GrMatrix vmi;
+ SkIntToScalar(drawState->getRenderTarget()->width()),
+ SkIntToScalar(drawState->getRenderTarget()->height()));
+ SkMatrix vmi;
// mapRect through persp matrix may not be correct
if (!drawState->getViewMatrix().hasPerspective() &&
drawState->getViewInverse(&vmi)) {
vmi.mapRect(&bounds);
} else {
- const GrMatrix& vm = drawState->getViewMatrix();
- if (!drawState->preConcatSamplerMatricesWithInverse(vm)) {
- GrPrintf("Could not invert matrix.\n");
- return false;
- }
- drawState->viewMatrix()->reset();
+ adcd.set(drawState);
}
} else {
bounds = path.getBounds();
@@ -491,28 +495,27 @@ bool GrDefaultPathRenderer::internalDrawPath(const SkPath& path,
}
bool GrDefaultPathRenderer::canDrawPath(const SkPath& path,
- GrPathFill fill,
+ const SkStrokeRec& stroke,
const GrDrawTarget* target,
bool antiAlias) const {
- // this class can draw any path with any fill but doesn't do any
- // anti-aliasing.
- return !antiAlias;
+ // this class can draw any path with any fill but doesn't do any anti-aliasing.
+ return (stroke.isFillStyle() || stroke.isHairlineStyle()) && !antiAlias;
}
bool GrDefaultPathRenderer::onDrawPath(const SkPath& path,
- GrPathFill fill,
+ const SkStrokeRec& stroke,
GrDrawTarget* target,
bool antiAlias) {
return this->internalDrawPath(path,
- fill,
+ stroke,
target,
false);
}
-void GrDefaultPathRenderer::drawPathToStencil(const SkPath& path,
- GrPathFill fill,
- GrDrawTarget* target) {
- GrAssert(kInverseEvenOdd_GrPathFill != fill);
- GrAssert(kInverseWinding_GrPathFill != fill);
- this->internalDrawPath(path, fill, target, true);
+void GrDefaultPathRenderer::onStencilPath(const SkPath& path,
+ const SkStrokeRec& stroke,
+ GrDrawTarget* target) {
+ GrAssert(SkPath::kInverseEvenOdd_FillType != path.getFillType());
+ GrAssert(SkPath::kInverseWinding_FillType != path.getFillType());
+ this->internalDrawPath(path, stroke, target, true);
}
diff --git a/src/gpu/GrDefaultPathRenderer.h b/src/gpu/GrDefaultPathRenderer.h
index e17f9cf7bf..e602fae25f 100644
--- a/src/gpu/GrDefaultPathRenderer.h
+++ b/src/gpu/GrDefaultPathRenderer.h
@@ -12,48 +12,46 @@
#include "SkTemplates.h"
/**
- * Subclass that renders the path using the stencil buffer to resolve fill
- * rules (e.g. winding, even-odd)
+ * Subclass that renders the path using the stencil buffer to resolve fill rules
+ * (e.g. winding, even-odd)
*/
class GR_API GrDefaultPathRenderer : public GrPathRenderer {
public:
- GrDefaultPathRenderer(bool separateStencilSupport,
- bool stencilWrapOpsSupport);
+ GrDefaultPathRenderer(bool separateStencilSupport, bool stencilWrapOpsSupport);
-
- virtual bool requiresStencilPass(const SkPath& path,
- GrPathFill fill,
- const GrDrawTarget* target) const SK_OVERRIDE;
-
- virtual bool canDrawPath(const SkPath& path,
- GrPathFill fill,
- const GrDrawTarget* target,
- bool antiAlias) const SK_OVERRIDE;
-
- virtual void drawPathToStencil(const SkPath& path,
- GrPathFill fill,
- GrDrawTarget* target) SK_OVERRIDE;
+ virtual bool canDrawPath(const SkPath&,
+ const SkStrokeRec&,
+ const GrDrawTarget*,
+ bool antiAlias) const SK_OVERRIDE;
private:
- virtual bool onDrawPath(const SkPath& path,
- GrPathFill fill,
- GrDrawTarget* target,
+ virtual StencilSupport onGetStencilSupport(const SkPath&,
+ const SkStrokeRec&,
+ const GrDrawTarget*) const SK_OVERRIDE;
+
+ virtual bool onDrawPath(const SkPath&,
+ const SkStrokeRec&,
+ GrDrawTarget*,
bool antiAlias) SK_OVERRIDE;
- bool internalDrawPath(const SkPath& path,
- GrPathFill fill,
- GrDrawTarget* target,
+ virtual void onStencilPath(const SkPath&,
+ const SkStrokeRec&,
+ GrDrawTarget*) SK_OVERRIDE;
+
+ bool internalDrawPath(const SkPath&,
+ const SkStrokeRec&,
+ GrDrawTarget*,
bool stencilOnly);
- bool createGeom(const SkPath& path,
- GrPathFill fill,
- GrScalar srcSpaceTol,
- GrDrawTarget* target,
- GrPrimitiveType* primType,
+ bool createGeom(const SkPath&,
+ const SkStrokeRec&,
+ SkScalar srcSpaceTol,
+ GrDrawTarget*,
+ GrPrimitiveType*,
int* vertexCnt,
int* indexCnt,
- GrDrawTarget::AutoReleaseGeometry* arg);
+ GrDrawTarget::AutoReleaseGeometry*);
bool fSeparateStencil;
bool fStencilWrapOps;
diff --git a/src/gpu/GrDrawState.cpp b/src/gpu/GrDrawState.cpp
index cc11f6cd87..bdb12302a1 100644
--- a/src/gpu/GrDrawState.cpp
+++ b/src/gpu/GrDrawState.cpp
@@ -13,7 +13,7 @@ void GrDrawState::setFromPaint(const GrPaint& paint) {
for (int i = 0; i < GrPaint::kMaxColorStages; ++i) {
int s = i + GrPaint::kFirstColorStage;
if (paint.isColorStageEnabled(i)) {
- *this->sampler(s) = paint.getColorSampler(i);
+ *this->stage(s) = paint.getColorStage(i);
}
}
@@ -22,7 +22,7 @@ void GrDrawState::setFromPaint(const GrPaint& paint) {
for (int i = 0; i < GrPaint::kMaxCoverageStages; ++i) {
int s = i + GrPaint::kFirstCoverageStage;
if (paint.isCoverageStageEnabled(i)) {
- *this->sampler(s) = paint.getCoverageSampler(i);
+ *this->stage(s) = paint.getCoverageStage(i);
}
}
@@ -36,12 +36,6 @@ void GrDrawState::setFromPaint(const GrPaint& paint) {
this->setState(GrDrawState::kDither_StateBit, paint.isDither());
this->setState(GrDrawState::kHWAntialias_StateBit, paint.isAntiAlias());
- if (paint.isColorMatrixEnabled()) {
- this->enableState(GrDrawState::kColorMatrix_StateBit);
- this->setColorMatrix(paint.getColorMatrix());
- } else {
- this->disableState(GrDrawState::kColorMatrix_StateBit);
- }
this->setBlendFunc(paint.getSrcBlendCoeff(), paint.getDstBlendCoeff());
this->setColorFilter(paint.getColorFilterColor(), paint.getColorFilterMode());
this->setCoverage(paint.getCoverage());
@@ -54,7 +48,7 @@ void GrDrawState::AutoViewMatrixRestore::restore() {
fDrawState->setViewMatrix(fViewMatrix);
for (int s = 0; s < GrDrawState::kNumStages; ++s) {
if (fRestoreMask & (1 << s)) {
- fDrawState->sampler(s)->setMatrixDeprecated(fSamplerMatrices[s]);
+ fDrawState->stage(s)->restoreCoordChange(fSavedCoordChanges[s]);
}
}
}
@@ -62,7 +56,7 @@ void GrDrawState::AutoViewMatrixRestore::restore() {
}
void GrDrawState::AutoViewMatrixRestore::set(GrDrawState* drawState,
- const GrMatrix& preconcatMatrix,
+ const SkMatrix& preconcatMatrix,
uint32_t explicitCoordStageMask) {
this->restore();
@@ -77,8 +71,8 @@ void GrDrawState::AutoViewMatrixRestore::set(GrDrawState* drawState,
for (int s = 0; s < GrDrawState::kNumStages; ++s) {
if (!(explicitCoordStageMask & (1 << s)) && drawState->isStageEnabled(s)) {
fRestoreMask |= (1 << s);
- fSamplerMatrices[s] = drawState->sampler(s)->getMatrix();
- drawState->sampler(s)->preConcatMatrix(preconcatMatrix);
+ fDrawState->stage(s)->saveCoordChange(&fSavedCoordChanges[s]);
+ drawState->stage(s)->preConcatCoordChange(preconcatMatrix);
}
}
}
@@ -90,7 +84,7 @@ void GrDrawState::AutoDeviceCoordDraw::restore() {
fDrawState->setViewMatrix(fViewMatrix);
for (int s = 0; s < GrDrawState::kNumStages; ++s) {
if (fRestoreMask & (1 << s)) {
- fDrawState->sampler(s)->setMatrixDeprecated(fSamplerMatrices[s]);
+ fDrawState->stage(s)->restoreCoordChange(fSavedCoordChanges[s]);
}
}
}
@@ -110,7 +104,7 @@ bool GrDrawState::AutoDeviceCoordDraw::set(GrDrawState* drawState,
fViewMatrix = drawState->getViewMatrix();
fRestoreMask = 0;
- GrMatrix invVM;
+ SkMatrix invVM;
bool inverted = false;
for (int s = 0; s < GrDrawState::kNumStages; ++s) {
@@ -123,9 +117,9 @@ bool GrDrawState::AutoDeviceCoordDraw::set(GrDrawState* drawState,
inverted = true;
}
fRestoreMask |= (1 << s);
- GrSamplerState* sampler = drawState->sampler(s);
- fSamplerMatrices[s] = sampler->getMatrix();
- sampler->preConcatMatrix(invVM);
+ GrEffectStage* stage = drawState->stage(s);
+ stage->saveCoordChange(&fSavedCoordChanges[s]);
+ stage->preConcatCoordChange(invVM);
}
}
drawState->viewMatrix()->reset();
diff --git a/src/gpu/GrDrawState.h b/src/gpu/GrDrawState.h
index f3d5e37d20..1208b776ed 100644
--- a/src/gpu/GrDrawState.h
+++ b/src/gpu/GrDrawState.h
@@ -8,11 +8,11 @@
#ifndef GrDrawState_DEFINED
#define GrDrawState_DEFINED
+#include "GrBackendEffectFactory.h"
#include "GrColor.h"
-#include "GrMatrix.h"
-#include "GrNoncopyable.h"
+#include "SkMatrix.h"
#include "GrRefCnt.h"
-#include "GrSamplerState.h"
+#include "GrEffectStage.h"
#include "GrStencil.h"
#include "GrTexture.h"
#include "GrRenderTarget.h"
@@ -27,23 +27,29 @@ public:
SK_DECLARE_INST_COUNT(GrDrawState)
/**
- * Number of texture stages. Each stage takes as input a color and
- * 2D texture coordinates. The color input to the first enabled stage is the
- * per-vertex color or the constant color (setColor/setAlpha) if there are
- * no per-vertex colors. For subsequent stages the input color is the output
- * color from the previous enabled stage. The output color of each stage is
- * the input color modulated with the result of a texture lookup. Texture
- * lookups are specified by a texture a sampler (setSamplerState). Texture
- * coordinates for each stage come from the vertices based on a
- * GrVertexLayout bitfield. The output fragment color is the output color of
- * the last enabled stage. The presence or absence of texture coordinates
- * for each stage in the vertex layout indicates whether a stage is enabled
- * or not.
+ * Total number of effect stages. Each stage can host a GrEffect. A stage is enabled if it has a
+ * GrEffect. The effect produces an output color in the fragment shader. It's inputs are the
+ * output from the previous enabled stage and a position. The position is either derived from
+ * the interpolated vertex positions or explicit per-vertex coords, depending upon the
+ * GrVertexLayout used to draw.
*
- * Stages 0 through GrPaint::kTotalStages-1 are reserved for setting up
- * the draw (i.e., textures and filter masks). Stages GrPaint::kTotalStages
- * through kNumStages-2 are earmarked for use by GrTextContext and
- * GrPathRenderer-derived classes. kNumStages-1 is earmarked for clipping
+ * The stages are divided into two sets, color-computing and coverage-computing. The final color
+ * stage produces the final pixel color. The coverage-computing stages function exactly as the
+ * color-computing but the output of the final coverage stage is treated as a fractional pixel
+ * coverage rather than as input to the src/dst color blend step.
+ *
+ * The input color to the first enabled color-stage is either the constant color or interpolated
+ * per-vertex colors, depending upon GrVertexLayout. The input to the first coverage stage is
+ * either a constant coverage (usually full-coverage), interpolated per-vertex coverage, or
+ * edge-AA computed coverage. (This latter is going away as soon as it can be rewritten as a
+ * GrEffect).
+ *
+ * See the documentation of kCoverageDrawing_StateBit for information about disabling the
+ * the color / coverage distinction.
+ *
+ * Stages 0 through GrPaint::kTotalStages-1 are reserved for stages copied from the client's
+ * GrPaint. Stages GrPaint::kTotalStages through kNumStages-2 are earmarked for use by
+ * GrTextContext and GrPathRenderer-derived classes. kNumStages-1 is earmarked for clipping
* by GrClipMaskManager.
*/
enum {
@@ -70,8 +76,7 @@ public:
/**
* Resets to the default state.
- * Sampler states *will* be modified: textures or CustomStage objects
- * will be released.
+ * GrEffects will be removed from all stages.
*/
void reset() {
@@ -190,41 +195,40 @@ public:
/**
* Creates a GrSingleTextureEffect.
*/
- void createTextureEffect(int stage, GrTexture* texture) {
- GrAssert(!this->getSampler(stage).getCustomStage());
- this->sampler(stage)->setCustomStage(
- SkNEW_ARGS(GrSingleTextureEffect, (texture)))->unref();
+ void createTextureEffect(int stageIdx, GrTexture* texture) {
+ GrAssert(!this->getStage(stageIdx).getEffect());
+ this->stage(stageIdx)->setEffect(SkNEW_ARGS(GrSingleTextureEffect, (texture)))->unref();
}
- void createTextureEffect(int stage, GrTexture* texture, const GrMatrix& matrix) {
- GrAssert(!this->getSampler(stage).getCustomStage());
- GrCustomStage* customStage = SkNEW_ARGS(GrSingleTextureEffect, (texture));
- this->sampler(stage)->setCustomStage(customStage, matrix)->unref();
+ void createTextureEffect(int stageIdx, GrTexture* texture, const SkMatrix& matrix) {
+ GrAssert(!this->getStage(stageIdx).getEffect());
+ GrEffect* effect = SkNEW_ARGS(GrSingleTextureEffect, (texture, matrix));
+ this->stage(stageIdx)->setEffect(effect)->unref();
}
- void createTextureEffect(int stage, GrTexture* texture,
- const GrMatrix& matrix,
+ void createTextureEffect(int stageIdx,
+ GrTexture* texture,
+ const SkMatrix& matrix,
const GrTextureParams& params) {
- GrAssert(!this->getSampler(stage).getCustomStage());
- GrCustomStage* customStage = SkNEW_ARGS(GrSingleTextureEffect, (texture, params));
- this->sampler(stage)->setCustomStage(customStage, matrix)->unref();
+ GrAssert(!this->getStage(stageIdx).getEffect());
+ GrEffect* effect = SkNEW_ARGS(GrSingleTextureEffect, (texture, matrix, params));
+ this->stage(stageIdx)->setEffect(effect)->unref();
}
bool stagesDisabled() {
for (int i = 0; i < kNumStages; ++i) {
- if (NULL != fSamplerStates[i].getCustomStage()) {
+ if (NULL != fStages[i].getEffect()) {
return false;
}
}
return true;
}
- void disableStage(int index) {
- fSamplerStates[index].setCustomStage(NULL);
+ void disableStage(int stageIdx) {
+ fStages[stageIdx].setEffect(NULL);
}
/**
- * Release all the textures and custom stages referred to by this
- * draw state.
+ * Release all the GrEffects referred to by this draw state.
*/
void disableStages() {
for (int i = 0; i < kNumStages; ++i) {
@@ -247,52 +251,53 @@ public:
/// @}
///////////////////////////////////////////////////////////////////////////
- /// @name Samplers
+ /// @name Stages
////
/**
- * Returns the current sampler for a stage.
+ * Returns the current stage by index.
*/
- const GrSamplerState& getSampler(int stage) const {
- GrAssert((unsigned)stage < kNumStages);
- return fSamplerStates[stage];
+ const GrEffectStage& getStage(int stageIdx) const {
+ GrAssert((unsigned)stageIdx < kNumStages);
+ return fStages[stageIdx];
}
/**
- * Writable pointer to a stage's sampler.
+ * Writable pointer to a stage.
*/
- GrSamplerState* sampler(int stage) {
- GrAssert((unsigned)stage < kNumStages);
- return fSamplerStates + stage;
+ GrEffectStage* stage(int stageIdx) {
+ GrAssert((unsigned)stageIdx < kNumStages);
+ return fStages + stageIdx;
}
/**
- * Preconcats the matrix of all samplers of enabled stages with a matrix.
+ * Called when the source coord system is changing. preConcat gives the transformation from the
+ * old coord system to the new coord system.
*/
- void preConcatSamplerMatrices(const GrMatrix& matrix) {
+ void preConcatStageMatrices(const SkMatrix& preConcat) {
for (int i = 0; i < kNumStages; ++i) {
if (this->isStageEnabled(i)) {
- fSamplerStates[i].preConcatMatrix(matrix);
+ fStages[i].preConcatCoordChange(preConcat);
}
}
}
/**
- * Preconcats the matrix of all samplers in the mask with the inverse of a
- * matrix. If the matrix inverse cannot be computed (and there is at least
- * one enabled stage) then false is returned.
+ * Called when the source coord system is changing. preConcatInverse is the inverse of the
+ * transformation from the old coord system to the new coord system. Returns false if the matrix
+ * cannot be inverted.
*/
- bool preConcatSamplerMatricesWithInverse(const GrMatrix& matrix) {
- GrMatrix inv;
+ bool preConcatStageMatricesWithInverse(const SkMatrix& preConcatInverse) {
+ SkMatrix inv;
bool computed = false;
for (int i = 0; i < kNumStages; ++i) {
if (this->isStageEnabled(i)) {
- if (!computed && !matrix.invert(&inv)) {
+ if (!computed && !preConcatInverse.invert(&inv)) {
return false;
} else {
computed = true;
}
- fSamplerStates[i].preConcatMatrix(inv);
+ fStages[i].preConcatCoordChange(preConcatInverse);
}
}
return true;
@@ -413,12 +418,12 @@ public:
* fully covers the render target. (w and h are the width and height of the
* the rendertarget.)
*/
- void setViewMatrix(const GrMatrix& m) { fViewMatrix = m; }
+ void setViewMatrix(const SkMatrix& m) { fViewMatrix = m; }
/**
* Gets a writable pointer to the view matrix.
*/
- GrMatrix* viewMatrix() { return &fViewMatrix; }
+ SkMatrix* viewMatrix() { return &fViewMatrix; }
/**
* Multiplies the current view matrix by a matrix
@@ -430,7 +435,7 @@ public:
*
* @param m the matrix used to modify the view matrix.
*/
- void preConcatViewMatrix(const GrMatrix& m) { fViewMatrix.preConcat(m); }
+ void preConcatViewMatrix(const SkMatrix& m) { fViewMatrix.preConcat(m); }
/**
* Multiplies the current view matrix by a matrix
@@ -442,13 +447,13 @@ public:
*
* @param m the matrix used to modify the view matrix.
*/
- void postConcatViewMatrix(const GrMatrix& m) { fViewMatrix.postConcat(m); }
+ void postConcatViewMatrix(const SkMatrix& m) { fViewMatrix.postConcat(m); }
/**
* Retrieves the current view matrix
* @return the current view matrix.
*/
- const GrMatrix& getViewMatrix() const { return fViewMatrix; }
+ const SkMatrix& getViewMatrix() const { return fViewMatrix; }
/**
* Retrieves the inverse of the current view matrix.
@@ -459,10 +464,10 @@ public:
*
* @param matrix if not null, will receive a copy of the current inverse.
*/
- bool getViewInverse(GrMatrix* matrix) const {
+ bool getViewInverse(SkMatrix* matrix) const {
// TODO: determine whether we really need to leave matrix unmodified
// at call sites when inversion fails.
- GrMatrix inverse;
+ SkMatrix inverse;
if (fViewMatrix.invert(&inverse)) {
if (matrix) {
*matrix = inverse;
@@ -476,14 +481,14 @@ public:
/**
* Preconcats the current view matrix and restores the previous view matrix in the destructor.
- * Stage matrices are automatically adjusted to compensate.
+ * Effect matrices are automatically adjusted to compensate.
*/
class AutoViewMatrixRestore : public ::GrNoncopyable {
public:
AutoViewMatrixRestore() : fDrawState(NULL) {}
AutoViewMatrixRestore(GrDrawState* ds,
- const GrMatrix& preconcatMatrix,
+ const SkMatrix& preconcatMatrix,
uint32_t explicitCoordStageMask = 0) {
fDrawState = NULL;
this->set(ds, preconcatMatrix, explicitCoordStageMask);
@@ -497,16 +502,16 @@ public:
void restore();
void set(GrDrawState* drawState,
- const GrMatrix& preconcatMatrix,
+ const SkMatrix& preconcatMatrix,
uint32_t explicitCoordStageMask = 0);
bool isSet() const { return NULL != fDrawState; }
private:
- GrDrawState* fDrawState;
- GrMatrix fViewMatrix;
- GrMatrix fSamplerMatrices[GrDrawState::kNumStages];
- uint32_t fRestoreMask;
+ GrDrawState* fDrawState;
+ SkMatrix fViewMatrix;
+ GrEffectStage::SavedCoordChange fSavedCoordChanges[GrDrawState::kNumStages];
+ uint32_t fRestoreMask;
};
////////////////////////////////////////////////////////////////////////////
@@ -546,7 +551,7 @@ public:
* Returns the matrix that was set previously set on the drawState. This is only valid
* if succeeded returns true.
*/
- const GrMatrix& getOriginalMatrix() const {
+ const SkMatrix& getOriginalMatrix() const {
GrAssert(this->succeeded());
return fViewMatrix;
}
@@ -557,10 +562,10 @@ public:
void restore();
private:
- GrDrawState* fDrawState;
- GrMatrix fViewMatrix;
- GrMatrix fSamplerMatrices[GrDrawState::kNumStages];
- uint32_t fRestoreMask;
+ GrDrawState* fDrawState;
+ SkMatrix fViewMatrix;
+ GrEffectStage::SavedCoordChange fSavedCoordChanges[GrDrawState::kNumStages];
+ uint32_t fRestoreMask;
};
/// @}
@@ -651,22 +656,6 @@ public:
/// @}
///////////////////////////////////////////////////////////////////////////
- /// @name Color Matrix
- ////
-
- /**
- * Sets the color matrix to use for the next draw.
- * @param matrix the 5x4 matrix to apply to the incoming color
- */
- void setColorMatrix(const float matrix[20]) {
- memcpy(fColorMatrix, matrix, sizeof(fColorMatrix));
- }
-
- const float* getColorMatrix() const { return fColorMatrix; }
-
- /// @}
-
- ///////////////////////////////////////////////////////////////////////////
// @name Edge AA
// Edge equations can be specified to perform anti-aliasing. Because the
// edges are specified as per-vertex data, vertices that are shared by
@@ -676,7 +665,7 @@ public:
/**
* When specifying edges as vertex data this enum specifies what type of
- * edges are in use. The edges are always 4 GrScalars in memory, even when
+ * edges are in use. The edges are always 4 SkScalars in memory, even when
* the edge type requires fewer than 4.
*
* TODO: Fix the fact that HairLine and Circle edge types use y-down coords.
@@ -729,9 +718,9 @@ public:
*/
kDither_StateBit = 0x01,
/**
- * Perform HW anti-aliasing. This means either HW FSAA, if supported
- * by the render target, or smooth-line rendering if a line primitive
- * is drawn and line smoothing is supported by the 3D API.
+ * Perform HW anti-aliasing. This means either HW FSAA, if supported by the render target,
+ * or smooth-line rendering if a line primitive is drawn and line smoothing is supported by
+ * the 3D API.
*/
kHWAntialias_StateBit = 0x02,
/**
@@ -743,11 +732,16 @@ public:
* operations.
*/
kNoColorWrites_StateBit = 0x08,
+
/**
- * Draws will apply the color matrix, otherwise the color matrix is
- * ignored.
+ * Usually coverage is applied after color blending. The color is blended using the coeffs
+ * specified by setBlendFunc(). The blended color is then combined with dst using coeffs
+ * of src_coverage, 1-src_coverage. Sometimes we are explicitly drawing a coverage mask. In
+ * this case there is no distinction between coverage and color and the caller needs direct
+ * control over the blend coeffs. When set, there will be a single blend step controlled by
+ * setBlendFunc() which will use coverage*color as the src color.
*/
- kColorMatrix_StateBit = 0x10,
+ kCoverageDrawing_StateBit = 0x10,
// Users of the class may add additional bits to the vector
kDummyStateBit,
@@ -806,6 +800,10 @@ public:
return 0 != (fFlagBits & kNoColorWrites_StateBit);
}
+ bool isCoverageDrawing() const {
+ return 0 != (fFlagBits & kCoverageDrawing_StateBit);
+ }
+
bool isStateFlagEnabled(uint32_t stateBit) const {
return 0 != (stateBit & fFlagBits);
}
@@ -846,7 +844,7 @@ public:
bool isStageEnabled(int s) const {
GrAssert((unsigned)s < kNumStages);
- return (NULL != fSamplerStates[s].getCustomStage());
+ return (NULL != fStages[s].getEffect());
}
// Most stages are usually not used, so conditionals here
@@ -874,18 +872,10 @@ public:
if (enabled != s.isStageEnabled(i)) {
return false;
}
- if (enabled && this->fSamplerStates[i] != s.fSamplerStates[i]) {
+ if (enabled && this->fStages[i] != s.fStages[i]) {
return false;
}
}
- if (kColorMatrix_StateBit & s.fFlagBits) {
- if (memcmp(fColorMatrix,
- s.fColorMatrix,
- sizeof(fColorMatrix))) {
- return false;
- }
- }
-
return true;
}
bool operator !=(const GrDrawState& s) const { return !(*this == s); }
@@ -910,14 +900,10 @@ public:
for (int i = 0; i < kNumStages; i++) {
if (s.isStageEnabled(i)) {
- this->fSamplerStates[i] = s.fSamplerStates[i];
+ this->fStages[i] = s.fStages[i];
}
}
- if (kColorMatrix_StateBit & s.fFlagBits) {
- memcpy(this->fColorMatrix, s.fColorMatrix, sizeof(fColorMatrix));
- }
-
return *this;
}
@@ -925,7 +911,7 @@ private:
// These fields are roughly sorted by decreasing likelihood of being different in op==
GrColor fColor;
- GrMatrix fViewMatrix;
+ SkMatrix fViewMatrix;
GrRenderTarget* fRenderTarget;
GrBlendCoeff fSrcBlend;
GrBlendCoeff fDstBlend;
@@ -941,9 +927,7 @@ private:
// This field must be last; it will not be copied or compared
// if the corresponding fTexture[] is NULL.
- GrSamplerState fSamplerStates[kNumStages];
- // only compared if the color matrix enable flag is set
- float fColorMatrix[20]; // 5 x 4 matrix
+ GrEffectStage fStages[kNumStages];
typedef GrRefCnt INHERITED;
};
diff --git a/src/gpu/GrDrawTarget.cpp b/src/gpu/GrDrawTarget.cpp
index 3b27172fb4..f80a4416f3 100644
--- a/src/gpu/GrDrawTarget.cpp
+++ b/src/gpu/GrDrawTarget.cpp
@@ -10,11 +10,12 @@
#include "GrDrawTarget.h"
#include "GrGpuVertex.h"
-#include "GrIndexBuffer.h"
#include "GrRenderTarget.h"
#include "GrTexture.h"
#include "GrVertexBuffer.h"
+#include "SkStrokeRec.h"
+
SK_DEFINE_INST_COUNT(GrDrawTarget)
namespace {
@@ -126,7 +127,7 @@ size_t GrDrawTarget::VertexSize(GrVertexLayout vertexLayout) {
size += sizeof(GrColor);
}
if (vertexLayout & kEdge_VertexLayoutBit) {
- size += 4 * sizeof(GrScalar);
+ size += 4 * sizeof(SkScalar);
}
return size;
}
@@ -146,13 +147,13 @@ size_t GrDrawTarget::VertexSize(GrVertexLayout vertexLayout) {
* Coverage
*/
-int GrDrawTarget::VertexStageCoordOffset(int stage, GrVertexLayout vertexLayout) {
+int GrDrawTarget::VertexStageCoordOffset(int stageIdx, GrVertexLayout vertexLayout) {
GrAssert(check_layout(vertexLayout));
- if (!StageUsesTexCoords(vertexLayout, stage)) {
+ if (!StageUsesTexCoords(vertexLayout, stageIdx)) {
return 0;
}
- int tcIdx = VertexTexCoordsForStage(stage, vertexLayout);
+ int tcIdx = VertexTexCoordsForStage(stageIdx, vertexLayout);
if (tcIdx >= 0) {
int vecSize = (vertexLayout & kTextFormat_VertexLayoutBit) ?
@@ -269,7 +270,7 @@ int GrDrawTarget::VertexSizeAndOffsetsByIdx(
if (NULL != edgeOffset) {
*edgeOffset = size;
}
- size += 4 * sizeof(GrScalar);
+ size += 4 * sizeof(SkScalar);
} else {
if (NULL != edgeOffset) {
*edgeOffset = -1;
@@ -313,17 +314,17 @@ bool GrDrawTarget::VertexUsesTexCoordIdx(int coordIndex,
return !!(gTexCoordMasks[coordIndex] & vertexLayout);
}
-int GrDrawTarget::VertexTexCoordsForStage(int stage,
+int GrDrawTarget::VertexTexCoordsForStage(int stageIdx,
GrVertexLayout vertexLayout) {
- GrAssert(stage < GrDrawState::kNumStages);
+ GrAssert(stageIdx < GrDrawState::kNumStages);
GrAssert(check_layout(vertexLayout));
- int bit = vertexLayout & gStageTexCoordMasks[stage];
+ int bit = vertexLayout & gStageTexCoordMasks[stageIdx];
if (bit) {
// figure out which set of texture coordates is used
// bits are ordered T0S0, T0S1, T0S2, ..., T1S0, T1S1, ...
// and start at bit 0.
GR_STATIC_ASSERT(sizeof(GrVertexLayout) <= sizeof(uint32_t));
- return (32 - Gr_clz(bit) - 1) / GrDrawState::kNumStages;
+ return (32 - SkCLZ(bit) - 1) / GrDrawState::kNumStages;
}
return -1;
}
@@ -542,8 +543,8 @@ bool GrDrawTarget::reserveIndexSpace(int indexCount,
}
-bool GrDrawTarget::StageUsesTexCoords(GrVertexLayout layout, int stage) {
- return SkToBool(layout & gStageTexCoordMasks[stage]);
+bool GrDrawTarget::StageUsesTexCoords(GrVertexLayout layout, int stageIdx) {
+ return SkToBool(layout & gStageTexCoordMasks[stageIdx]);
}
bool GrDrawTarget::reserveVertexAndIndexSpace(GrVertexLayout vertexLayout,
@@ -748,10 +749,10 @@ bool GrDrawTarget::checkDraw(GrPrimitiveType type, int startVertex,
GrAssert(NULL != drawState.getRenderTarget());
for (int s = 0; s < GrDrawState::kNumStages; ++s) {
if (drawState.isStageEnabled(s)) {
- const GrCustomStage* stage = drawState.getSampler(s).getCustomStage();
- int numTextures = stage->numTextures();
+ const GrEffect* effect = drawState.getStage(s).getEffect();
+ int numTextures = effect->numTextures();
for (int t = 0; t < numTextures; ++t) {
- GrTexture* texture = stage->texture(t);
+ GrTexture* texture = effect->texture(t);
GrAssert(texture->asRenderTarget() != drawState.getRenderTarget());
}
}
@@ -783,13 +784,13 @@ void GrDrawTarget::drawNonIndexed(GrPrimitiveType type,
}
}
-void GrDrawTarget::stencilPath(const GrPath* path, GrPathFill fill) {
+void GrDrawTarget::stencilPath(const GrPath* path, const SkStrokeRec& stroke, SkPath::FillType fill) {
// TODO: extract portions of checkDraw that are relevant to path stenciling.
GrAssert(NULL != path);
GrAssert(fCaps.pathStencilingSupport());
- GrAssert(kHairLine_GrPathFill != fill);
- GrAssert(!GrIsFillInverted(fill));
- this->onStencilPath(path, fill);
+ GrAssert(!stroke.isHairlineStyle());
+ GrAssert(!SkPath::IsInverseFillType(fill));
+ this->onStencilPath(path, stroke, fill);
}
////////////////////////////////////////////////////////////////////////////////
@@ -804,14 +805,15 @@ bool GrDrawTarget::canTweakAlphaForCoverage() const {
* We want the blend to compute: f*Cs*S + (f*Cd + (1-f))D
* By tweaking the source color's alpha we're replacing S with S'=fS. It's
* obvious that that first term will always be ok. The second term can be
- * rearranged as [1-(1-Cd)f]D. By substituing in the various possbilities
+ * rearranged as [1-(1-Cd)f]D. By substituting in the various possibilities
* for Cd we find that only 1, ISA, and ISC produce the correct depth
- * coeffecient in terms of S' and D.
+ * coefficient in terms of S' and D.
*/
GrBlendCoeff dstCoeff = this->getDrawState().getDstBlendCoeff();
return kOne_GrBlendCoeff == dstCoeff ||
kISA_GrBlendCoeff == dstCoeff ||
- kISC_GrBlendCoeff == dstCoeff;
+ kISC_GrBlendCoeff == dstCoeff ||
+ this->getDrawState().isCoverageDrawing();
}
bool GrDrawTarget::srcAlphaWillBeOne(GrVertexLayout layout) const {
@@ -828,14 +830,24 @@ bool GrDrawTarget::srcAlphaWillBeOne(GrVertexLayout layout) const {
if (SkXfermode::kDst_Mode != drawState.getColorFilterMode()) {
return false;
}
+ int stageCnt;
+ // Check whether coverage is treated as color
+ if (drawState.isCoverageDrawing()) {
+ if (0xff != GrColorUnpackA(drawState.getCoverage())) {
+ return false;
+ }
+ stageCnt = GrDrawState::kNumStages;
+ } else {
+ stageCnt = drawState.getFirstCoverageStage();
+ }
// Check if a color stage could create a partial alpha
- for (int s = 0; s < drawState.getFirstCoverageStage(); ++s) {
- if (this->isStageEnabled(s)) {
- const GrCustomStage* stage = drawState.getSampler(s).getCustomStage();
- // FIXME: The param indicates whether the texture is opaque or not. However, the stage
+ for (int s = 0; s < stageCnt; ++s) {
+ const GrEffect* effect = drawState.getStage(s).getEffect();
+ if (NULL != effect) {
+ // FIXME: The param indicates whether the texture is opaque or not. However, the effect
// already controls its textures. It really needs to know whether the incoming color
// (from a uni, per-vertex colors, or previous stage) is opaque or not.
- if (!stage->isOpaque(true)) {
+ if (!effect->isOpaque(true)) {
return false;
}
}
@@ -875,17 +887,6 @@ GrDrawTarget::getBlendOpts(bool forceCoverage,
}
*dstCoeff = drawState.getDstBlendCoeff();
- // We don't ever expect source coeffecients to reference the source
- GrAssert(kSA_GrBlendCoeff != *srcCoeff &&
- kISA_GrBlendCoeff != *srcCoeff &&
- kSC_GrBlendCoeff != *srcCoeff &&
- kISC_GrBlendCoeff != *srcCoeff);
- // same for dst
- GrAssert(kDA_GrBlendCoeff != *dstCoeff &&
- kIDA_GrBlendCoeff != *dstCoeff &&
- kDC_GrBlendCoeff != *dstCoeff &&
- kIDC_GrBlendCoeff != *dstCoeff);
-
if (drawState.isColorWriteDisabled()) {
*srcCoeff = kZero_GrBlendCoeff;
*dstCoeff = kOne_GrBlendCoeff;
@@ -897,13 +898,13 @@ GrDrawTarget::getBlendOpts(bool forceCoverage,
bool dstCoeffIsZero = kZero_GrBlendCoeff == *dstCoeff ||
(kISA_GrBlendCoeff == *dstCoeff && srcAIsOne);
-
+ bool covIsZero = !drawState.isCoverageDrawing() &&
+ !(layout & kCoverage_VertexLayoutBit) &&
+ 0 == drawState.getCoverage();
// When coeffs are (0,1) there is no reason to draw at all, unless
// stenciling is enabled. Having color writes disabled is effectively
// (0,1). The same applies when coverage is known to be 0.
- if ((kZero_GrBlendCoeff == *srcCoeff && dstCoeffIsOne) ||
- (!(layout & kCoverage_VertexLayoutBit) &&
- 0 == drawState.getCoverage())) {
+ if ((kZero_GrBlendCoeff == *srcCoeff && dstCoeffIsOne) || covIsZero) {
if (drawState.getStencil().doesWrite()) {
return kDisableBlend_BlendOptFlag |
kEmitTransBlack_BlendOptFlag;
@@ -913,7 +914,7 @@ GrDrawTarget::getBlendOpts(bool forceCoverage,
}
// check for coverage due to constant coverage, per-vertex coverage,
- // edge aa or coverage texture stage
+ // edge aa or coverage stage
bool hasCoverage = forceCoverage ||
0xffffffff != drawState.getCoverage() ||
(layout & kCoverage_VertexLayoutBit) ||
@@ -939,10 +940,12 @@ GrDrawTarget::getBlendOpts(bool forceCoverage,
// or blend, just write transparent black into the dst.
*srcCoeff = kOne_GrBlendCoeff;
*dstCoeff = kZero_GrBlendCoeff;
- return kDisableBlend_BlendOptFlag |
- kEmitTransBlack_BlendOptFlag;
+ return kDisableBlend_BlendOptFlag | kEmitTransBlack_BlendOptFlag;
}
}
+ } else if (drawState.isCoverageDrawing()) {
+ // we have coverage but we aren't distinguishing it from alpha by request.
+ return kCoverageAsAlpha_BlendOptFlag;
} else {
// check whether coverage can be safely rolled into alpha
// of if we can skip color computation and just emit coverage
@@ -1027,9 +1030,9 @@ void GrDrawTarget::drawIndexedInstances(GrPrimitiveType type,
////////////////////////////////////////////////////////////////////////////////
void GrDrawTarget::drawRect(const GrRect& rect,
- const GrMatrix* matrix,
+ const SkMatrix* matrix,
const GrRect* srcRects[],
- const GrMatrix* srcMatrices[]) {
+ const SkMatrix* srcMatrices[]) {
GrVertexLayout layout = GetRectVertexLayout(srcRects);
AutoReleaseGeometry geo(this, layout, 4, 0);
@@ -1070,9 +1073,9 @@ GrVertexLayout GrDrawTarget::GetRectVertexLayout(const GrRect* srcRects[]) {
// Note: the color parameter will only be used when kColor_VertexLayoutBit
// is present in 'layout'
void GrDrawTarget::SetRectVertices(const GrRect& rect,
- const GrMatrix* matrix,
+ const SkMatrix* matrix,
const GrRect* srcRects[],
- const GrMatrix* srcMatrices[],
+ const SkMatrix* srcMatrices[],
GrColor color,
GrVertexLayout layout,
void* vertices) {
@@ -1214,6 +1217,15 @@ void GrDrawTarget::AutoReleaseGeometry::reset() {
fIndices = NULL;
}
+GrDrawTarget::AutoClipRestore::AutoClipRestore(GrDrawTarget* target, const SkIRect& newClip) {
+ fTarget = target;
+ fClip = fTarget->getClip();
+ fStack.init();
+ fStack.get()->clipDevRect(newClip, SkRegion::kReplace_Op);
+ fReplacementClip.fClipStack = fStack.get();
+ target->setClip(&fReplacementClip);
+}
+
void GrDrawTarget::Caps::print() const {
static const char* gNY[] = {"NO", "YES"};
GrPrintf("8 Bit Palette Support : %s\n", gNY[fInternals.f8BitPaletteSupport]);
diff --git a/src/gpu/GrDrawTarget.h b/src/gpu/GrDrawTarget.h
index 134cccd9de..a54dd0f152 100644
--- a/src/gpu/GrDrawTarget.h
+++ b/src/gpu/GrDrawTarget.h
@@ -11,20 +11,25 @@
#ifndef GrDrawTarget_DEFINED
#define GrDrawTarget_DEFINED
+#include "GrClipData.h"
#include "GrDrawState.h"
#include "GrIndexBuffer.h"
-#include "GrMatrix.h"
+#include "SkMatrix.h"
#include "GrRefCnt.h"
#include "GrTemplates.h"
-#include "SkXfermode.h"
+#include "SkClipStack.h"
+#include "SkPath.h"
#include "SkTLazy.h"
#include "SkTArray.h"
+#include "SkXfermode.h"
class GrClipData;
class GrPath;
class GrVertexBuffer;
+class SkStrokeRec;
+
class GrDrawTarget : public GrRefCnt {
protected:
/** This helper class allows GrDrawTarget subclasses to set the caps values without having to be
@@ -190,18 +195,18 @@ public:
/**
* Generates a bit indicating that a texture stage uses texture coordinates
*
- * @param stage the stage that will use texture coordinates.
+ * @param stageIdx the stage that will use texture coordinates.
* @param texCoordIdx the index of the texture coordinates to use
*
* @return the bit to add to a GrVertexLayout bitfield.
*/
- static int StageTexCoordVertexLayoutBit(int stage, int texCoordIdx) {
- GrAssert(stage < GrDrawState::kNumStages);
+ static int StageTexCoordVertexLayoutBit(int stageIdx, int texCoordIdx) {
+ GrAssert(stageIdx < GrDrawState::kNumStages);
GrAssert(texCoordIdx < GrDrawState::kMaxTexCoords);
- return 1 << (stage + (texCoordIdx * GrDrawState::kNumStages));
+ return 1 << (stageIdx + (texCoordIdx * GrDrawState::kNumStages));
}
- static bool StageUsesTexCoords(GrVertexLayout layout, int stage);
+ static bool StageUsesTexCoords(GrVertexLayout layout, int stageIdx);
private:
// non-stage bits start at this index.
@@ -452,7 +457,7 @@ public:
* winding (not inverse or hairline). It will respect the HW antialias flag
* on the draw state (if possible in the 3D API).
*/
- void stencilPath(const GrPath*, GrPathFill);
+ void stencilPath(const GrPath*, const SkStrokeRec& stroke, SkPath::FillType fill);
/**
* Helper function for drawing rects. This does not use the current index
@@ -476,9 +481,21 @@ public:
* srcMatrices are desired.
*/
virtual void drawRect(const GrRect& rect,
- const GrMatrix* matrix,
+ const SkMatrix* matrix,
const GrRect* srcRects[],
- const GrMatrix* srcMatrices[]);
+ const SkMatrix* srcMatrices[]);
+ /**
+ * Helper for drawRect when the caller doesn't need separate src rects or
+ * matrices.
+ */
+ void drawSimpleRect(const GrRect& rect, const SkMatrix* matrix = NULL) {
+ drawRect(rect, matrix, NULL, NULL);
+ }
+ void drawSimpleRect(const GrIRect& irect, const SkMatrix* matrix = NULL) {
+ SkRect rect = SkRect::MakeFromIRect(irect);
+ this->drawRect(rect, matrix, NULL, NULL);
+ }
+
/**
* This call is used to draw multiple instances of some geometry with a
@@ -514,15 +531,6 @@ public:
int indicesPerInstance);
/**
- * Helper for drawRect when the caller doesn't need separate src rects or
- * matrices.
- */
- void drawSimpleRect(const GrRect& rect,
- const GrMatrix* matrix) {
- drawRect(rect, matrix, NULL, NULL);
- }
-
- /**
* Clear the current render target if one isn't passed in. Ignores the
* clip and all other draw state (blend mode, stages, etc). Clears the
* whole thing if rect is NULL, otherwise just the rect.
@@ -635,12 +643,16 @@ public:
fClip = fTarget->getClip();
}
+ AutoClipRestore(GrDrawTarget* target, const SkIRect& newClip);
+
~AutoClipRestore() {
fTarget->setClip(fClip);
}
private:
- GrDrawTarget* fTarget;
- const GrClipData* fClip;
+ GrDrawTarget* fTarget;
+ const GrClipData* fClip;
+ SkTLazy<SkClipStack> fStack;
+ GrClipData fReplacementClip;
};
////////////////////////////////////////////////////////////////////////////
@@ -674,13 +686,13 @@ public:
* as texture coordinates, in which case the result of the function is
* indistinguishable from the case when the stage is disabled.
*
- * @param stage the stage to query
+ * @param stageIdx the stage to query
* @param vertexLayout layout to query
*
* @return the texture coordinate index or -1 if the stage doesn't use
* separate (non-position) texture coordinates.
*/
- static int VertexTexCoordsForStage(int stage, GrVertexLayout vertexLayout);
+ static int VertexTexCoordsForStage(int stageIdx, GrVertexLayout vertexLayout);
/**
* Helper function to compute the offset of texture coordinates in a vertex
@@ -688,7 +700,7 @@ public:
* layout has no texture coordinates. Will be 0 if positions are
* used as texture coordinates for the stage.
*/
- static int VertexStageCoordOffset(int stage, GrVertexLayout vertexLayout);
+ static int VertexStageCoordOffset(int stageIdx, GrVertexLayout vertexLayout);
/**
* Helper function to compute the offset of the color in a vertex
@@ -883,14 +895,13 @@ protected:
};
GR_DECL_BITFIELD_OPS_FRIENDS(BlendOptFlags);
- // Determines what optimizations can be applied based on the blend.
- // The coeffecients may have to be tweaked in order for the optimization
- // to work. srcCoeff and dstCoeff are optional params that receive the
- // tweaked coeffecients.
- // Normally the function looks at the current state to see if coverage
- // is enabled. By setting forceCoverage the caller can speculatively
- // determine the blend optimizations that would be used if there was
- // partial pixel coverage
+ /**
+ * Determines what optimizations can be applied based on the blend. The coefficients may have
+ * to be tweaked in order for the optimization to work. srcCoeff and dstCoeff are optional
+ * params that receive the tweaked coefficients. Normally the function looks at the current
+ * state to see if coverage is enabled. By setting forceCoverage the caller can speculatively
+ * determine the blend optimizations that would be used if there was partial pixel coverage.
+ */
BlendOptFlags getBlendOpts(bool forceCoverage = false,
GrBlendCoeff* srcCoeff = NULL,
GrBlendCoeff* dstCoeff = NULL) const;
@@ -941,8 +952,8 @@ protected:
}
}
- bool isStageEnabled(int stage) const {
- return this->getDrawState().isStageEnabled(stage);
+ bool isStageEnabled(int stageIdx) const {
+ return this->getDrawState().isStageEnabled(stageIdx);
}
// A sublcass can optionally overload this function to be notified before
@@ -981,7 +992,7 @@ protected:
virtual void onDrawNonIndexed(GrPrimitiveType type,
int startVertex,
int vertexCount) = 0;
- virtual void onStencilPath(const GrPath*, GrPathFill) = 0;
+ virtual void onStencilPath(const GrPath*, const SkStrokeRec& stroke, SkPath::FillType fill) = 0;
// subclass overrides to be notified when clip is set. Must call
// INHERITED::clipwillBeSet
@@ -992,9 +1003,9 @@ protected:
static GrVertexLayout GetRectVertexLayout(const GrRect* srcRects[]);
static void SetRectVertices(const GrRect& rect,
- const GrMatrix* matrix,
+ const SkMatrix* matrix,
const GrRect* srcRects[],
- const GrMatrix* srcMatrices[],
+ const SkMatrix* srcMatrices[],
GrColor color,
GrVertexLayout layout,
void* vertices);
diff --git a/src/gpu/GrEffect.cpp b/src/gpu/GrEffect.cpp
new file mode 100644
index 0000000000..d470c9cc5f
--- /dev/null
+++ b/src/gpu/GrEffect.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrEffect.h"
+#include "GrBackendEffectFactory.h"
+#include "GrContext.h"
+#include "GrMemoryPool.h"
+#include "SkTLS.h"
+
+SK_DEFINE_INST_COUNT(GrEffect)
+
+#if SK_ALLOW_STATIC_GLOBAL_INITIALIZERS
+SkTArray<GrEffectTestFactory*, true>* GrEffectTestFactory::GetFactories() {
+ static SkTArray<GrEffectTestFactory*, true> gFactories;
+ return &gFactories;
+}
+#endif
+
+namespace GrEffectUnitTest {
+const SkMatrix& TestMatrix(SkRandom* random) {
+ static SkMatrix gMatrices[5];
+ static bool gOnce;
+ if (!gOnce) {
+ gMatrices[0].reset();
+ gMatrices[1].setTranslate(SkIntToScalar(-100), SkIntToScalar(100));
+ gMatrices[2].setRotate(SkIntToScalar(17));
+ gMatrices[3].setRotate(SkIntToScalar(185));
+ gMatrices[3].postTranslate(SkIntToScalar(66), SkIntToScalar(-33));
+ gMatrices[3].postScale(SkIntToScalar(2), SK_ScalarHalf);
+ gMatrices[4].setRotate(SkIntToScalar(215));
+ gMatrices[4].set(SkMatrix::kMPersp0, SkFloatToScalar(0.00013f));
+ gMatrices[4].set(SkMatrix::kMPersp1, SkFloatToScalar(-0.000039f));
+ gOnce = true;
+ }
+ return gMatrices[random->nextULessThan(static_cast<uint32_t>(SK_ARRAY_COUNT(gMatrices)))];
+}
+}
+
+class GrEffect_Globals {
+public:
+ static GrMemoryPool* GetTLS() {
+ return (GrMemoryPool*)SkTLS::Get(CreateTLS, DeleteTLS);
+ }
+
+private:
+ static void* CreateTLS() {
+ return SkNEW_ARGS(GrMemoryPool, (4096, 4096));
+ }
+
+ static void DeleteTLS(void* pool) {
+ SkDELETE(reinterpret_cast<GrMemoryPool*>(pool));
+ }
+};
+
+int32_t GrBackendEffectFactory::fCurrEffectClassID = GrBackendEffectFactory::kIllegalEffectClassID;
+
+GrEffect::GrEffect(int numTextures)
+ : fNumTextures(numTextures) {
+}
+
+GrEffect::~GrEffect() {
+
+}
+
+bool GrEffect::isOpaque(bool inputTextureIsOpaque) const {
+ return false;
+}
+
+const char* GrEffect::name() const {
+ return this->getFactory().name();
+}
+
+
+bool GrEffect::isEqual(const GrEffect& s) const {
+ if (this->numTextures() != s.numTextures()) {
+ return false;
+ }
+ for (int i = 0; i < this->numTextures(); ++i) {
+ if (this->textureAccess(i) != s.textureAccess(i)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+const GrTextureAccess& GrEffect::textureAccess(int index) const {
+ GrCrash("We shouldn't be calling this function on the base class.");
+ static GrTextureAccess kDummy;
+ return kDummy;
+}
+
+void * GrEffect::operator new(size_t size) {
+ return GrEffect_Globals::GetTLS()->allocate(size);
+}
+
+void GrEffect::operator delete(void* target) {
+ GrEffect_Globals::GetTLS()->release(target);
+}
diff --git a/src/gpu/GrGpu.cpp b/src/gpu/GrGpu.cpp
index 4bc1574f3f..b84b0f11c7 100644
--- a/src/gpu/GrGpu.cpp
+++ b/src/gpu/GrGpu.cpp
@@ -126,6 +126,10 @@ void GrGpu::unimpl(const char msg[]) {
GrTexture* GrGpu::createTexture(const GrTextureDesc& desc,
const void* srcData, size_t rowBytes) {
+ if (kUnknown_GrPixelConfig == desc.fConfig) {
+ return NULL;
+ }
+
this->handleDirtyContext();
GrTexture* tex = this->onCreateTexture(desc, srcData, rowBytes);
if (NULL != tex &&
@@ -173,9 +177,9 @@ bool GrGpu::attachStencilBufferToRenderTarget(GrRenderTarget* rt) {
}
}
-GrTexture* GrGpu::createPlatformTexture(const GrPlatformTextureDesc& desc) {
+GrTexture* GrGpu::wrapBackendTexture(const GrBackendTextureDesc& desc) {
this->handleDirtyContext();
- GrTexture* tex = this->onCreatePlatformTexture(desc);
+ GrTexture* tex = this->onWrapBackendTexture(desc);
if (NULL == tex) {
return NULL;
}
@@ -190,9 +194,9 @@ GrTexture* GrGpu::createPlatformTexture(const GrPlatformTextureDesc& desc) {
}
}
-GrRenderTarget* GrGpu::createPlatformRenderTarget(const GrPlatformRenderTargetDesc& desc) {
+GrRenderTarget* GrGpu::wrapBackendRenderTarget(const GrBackendRenderTargetDesc& desc) {
this->handleDirtyContext();
- return this->onCreatePlatformRenderTarget(desc);
+ return this->onWrapBackendRenderTarget(desc);
}
GrVertexBuffer* GrGpu::createVertexBuffer(uint32_t size, bool dynamic) {
@@ -303,14 +307,14 @@ const GrVertexBuffer* GrGpu::getUnitSquareVertexBuffer() const {
static const GrPoint DATA[] = {
{ 0, 0 },
- { GR_Scalar1, 0 },
- { GR_Scalar1, GR_Scalar1 },
- { 0, GR_Scalar1 }
+ { SK_Scalar1, 0 },
+ { SK_Scalar1, SK_Scalar1 },
+ { 0, SK_Scalar1 }
#if 0
GrPoint(0, 0),
- GrPoint(GR_Scalar1,0),
- GrPoint(GR_Scalar1,GR_Scalar1),
- GrPoint(0, GR_Scalar1)
+ GrPoint(SK_Scalar1,0),
+ GrPoint(SK_Scalar1,SK_Scalar1),
+ GrPoint(0, SK_Scalar1)
#endif
};
static const size_t SIZE = sizeof(DATA);
@@ -406,7 +410,7 @@ void GrGpu::onDrawNonIndexed(GrPrimitiveType type,
this->onGpuDrawNonIndexed(type, sVertex, vertexCount);
}
-void GrGpu::onStencilPath(const GrPath* path, GrPathFill fill) {
+void GrGpu::onStencilPath(const GrPath* path, const SkStrokeRec&, SkPath::FillType fill) {
this->handleDirtyContext();
// TODO: make this more effecient (don't copy and copy back)
diff --git a/src/gpu/GrGpu.h b/src/gpu/GrGpu.h
index 60e49e7c9e..4bc4c25f00 100644
--- a/src/gpu/GrGpu.h
+++ b/src/gpu/GrGpu.h
@@ -15,6 +15,8 @@
#include "GrRefCnt.h"
#include "GrClipMaskManager.h"
+#include "SkPath.h"
+
class GrContext;
class GrIndexBufferAllocPool;
class GrPath;
@@ -29,7 +31,7 @@ class GrGpu : public GrDrawTarget {
public:
/**
- * Additional blend coeffecients for dual source blending, not exposed
+ * Additional blend coefficients for dual source blending, not exposed
* through GrPaint/GrContext.
*/
enum ExtendedBlendCoeffs {
@@ -44,11 +46,10 @@ public:
};
/**
- * Create an instance of GrGpu that matches the specified Engine backend.
- * If the requested engine is not supported (at compile-time or run-time)
- * this returns NULL.
+ * Create an instance of GrGpu that matches the specified backend. If the requested backend is
+ * not supported (at compile-time or run-time) this returns NULL.
*/
- static GrGpu* Create(GrEngine, GrPlatform3DContext context3D);
+ static GrGpu* Create(GrBackend, GrBackendContext);
////////////////////////////////////////////////////////////////////////////
@@ -96,14 +97,14 @@ public:
const void* srcData, size_t rowBytes);
/**
- * Implements GrContext::createPlatformTexture
+ * Implements GrContext::wrapBackendTexture
*/
- GrTexture* createPlatformTexture(const GrPlatformTextureDesc& desc);
+ GrTexture* wrapBackendTexture(const GrBackendTextureDesc&);
/**
- * Implements GrContext::createPlatformTexture
+ * Implements GrContext::wrapBackendTexture
*/
- GrRenderTarget* createPlatformRenderTarget(const GrPlatformRenderTargetDesc& desc);
+ GrRenderTarget* wrapBackendRenderTarget(const GrBackendRenderTargetDesc&);
/**
* Creates a vertex buffer.
@@ -245,7 +246,7 @@ public:
* @param height height of rectangle to write in pixels.
* @param config the pixel config of the source buffer
* @param buffer memory to read pixels from
- * @param rowBytes number of bytes bewtween consecutive rows. Zero
+ * @param rowBytes number of bytes between consecutive rows. Zero
* means rows are tightly packed.
*/
void writeTexturePixels(GrTexture* texture,
@@ -443,23 +444,23 @@ protected:
virtual void onResetContext() = 0;
- // overridden by API-specific derived class to create objects.
+ // overridden by backend-specific derived class to create objects.
virtual GrTexture* onCreateTexture(const GrTextureDesc& desc,
const void* srcData,
size_t rowBytes) = 0;
- virtual GrTexture* onCreatePlatformTexture(const GrPlatformTextureDesc& desc) = 0;
- virtual GrRenderTarget* onCreatePlatformRenderTarget(const GrPlatformRenderTargetDesc& desc) = 0;
+ virtual GrTexture* onWrapBackendTexture(const GrBackendTextureDesc&) = 0;
+ virtual GrRenderTarget* onWrapBackendRenderTarget(const GrBackendRenderTargetDesc&) = 0;
virtual GrVertexBuffer* onCreateVertexBuffer(uint32_t size,
bool dynamic) = 0;
virtual GrIndexBuffer* onCreateIndexBuffer(uint32_t size,
bool dynamic) = 0;
virtual GrPath* onCreatePath(const SkPath& path) = 0;
- // overridden by API-specific derivated class to perform the clear and
+ // overridden by backend-specific derived class to perform the clear and
// clearRect. NULL rect means clear whole target.
virtual void onClear(const GrIRect* rect, GrColor color) = 0;
- // overridden by API-specific derived class to perform the draw call.
+ // overridden by backend-specific derived class to perform the draw call.
virtual void onGpuDrawIndexed(GrPrimitiveType type,
uint32_t startVertex,
uint32_t startIndex,
@@ -474,15 +475,15 @@ protected:
// necessary to stencil the path. These are still subject to filtering by
// the clip mask manager.
virtual void setStencilPathSettings(const GrPath&,
- GrPathFill,
+ SkPath::FillType,
GrStencilSettings* settings) = 0;
- // overridden by API-specific derived class to perform the path stenciling.
- virtual void onGpuStencilPath(const GrPath*, GrPathFill) = 0;
+ // overridden by backend-specific derived class to perform the path stenciling.
+ virtual void onGpuStencilPath(const GrPath*, SkPath::FillType) = 0;
- // overridden by API-specific derived class to perform flush
+ // overridden by backend-specific derived class to perform flush
virtual void onForceRenderTargetFlush() = 0;
- // overridden by API-specific derived class to perform the read pixels.
+ // overridden by backend-specific derived class to perform the read pixels.
virtual bool onReadPixels(GrRenderTarget* target,
int left, int top, int width, int height,
GrPixelConfig,
@@ -490,13 +491,13 @@ protected:
size_t rowBytes,
bool invertY) = 0;
- // overridden by API-specific derived class to perform the texture update
+ // overridden by backend-specific derived class to perform the texture update
virtual void onWriteTexturePixels(GrTexture* texture,
int left, int top, int width, int height,
GrPixelConfig config, const void* buffer,
size_t rowBytes) = 0;
- // overridden by API-specific derived class to perform the resolve
+ // overridden by backend-specific derived class to perform the resolve
virtual void onResolveRenderTarget(GrRenderTarget* target) = 0;
// called to program the vertex data, indexCount will be 0 if drawing non-
@@ -520,7 +521,7 @@ protected:
// The GrGpu typically records the clients requested state and then flushes
// deltas from previous state at draw time. This function does the
- // API-specific flush of the state
+ // backend-specific flush of the state
// returns false if current state is unsupported.
virtual bool flushGraphicsState(DrawType) = 0;
@@ -554,7 +555,7 @@ private:
bool fContextIsDirty;
- typedef SkTDLinkedList<GrResource> ResourceList;
+ typedef SkTInternalLList<GrResource> ResourceList;
ResourceList fResourceList;
// Given a rt, find or create a stencil buffer and attach it
@@ -569,7 +570,8 @@ private:
virtual void onDrawNonIndexed(GrPrimitiveType type,
int startVertex,
int vertexCount) SK_OVERRIDE;
- virtual void onStencilPath(const GrPath* path, GrPathFill fill) SK_OVERRIDE;
+ virtual void onStencilPath(const GrPath* path, const SkStrokeRec& stroke,
+ SkPath::FillType) SK_OVERRIDE;
// readies the pools to provide vertex/index data.
void prepareVertexPool();
diff --git a/src/gpu/GrGpuFactory.cpp b/src/gpu/GrGpuFactory.cpp
index 0d4c3814dd..63a73b738a 100644
--- a/src/gpu/GrGpuFactory.cpp
+++ b/src/gpu/GrGpuFactory.cpp
@@ -14,13 +14,13 @@
#include "GrGpu.h"
#include "gl/GrGpuGL.h"
-GrGpu* GrGpu::Create(GrEngine engine, GrPlatform3DContext context3D) {
+GrGpu* GrGpu::Create(GrBackend backend, GrBackendContext context) {
const GrGLInterface* glInterface = NULL;
SkAutoTUnref<const GrGLInterface> glInterfaceUnref;
- if (kOpenGL_Shaders_GrEngine == engine) {
- glInterface = reinterpret_cast<const GrGLInterface*>(context3D);
+ if (kOpenGL_GrBackend == backend) {
+ glInterface = reinterpret_cast<const GrGLInterface*>(context);
if (NULL == glInterface) {
glInterface = GrGLDefaultInterface();
// By calling GrGLDefaultInterface we've taken a ref on the
diff --git a/src/gpu/GrGpuVertex.h b/src/gpu/GrGpuVertex.h
index 570a77c885..a5e39e8ab2 100644
--- a/src/gpu/GrGpuVertex.h
+++ b/src/gpu/GrGpuVertex.h
@@ -20,12 +20,12 @@
#define GrFixedToTextScalar(x) (x)
#elif GR_TEXT_SCALAR_IS_FIXED
typedef GrFixed GrTextScalar;
- #define GrIntToTextScalar(x) GrIntToFixed(x)
+ #define GrIntToTextScalar(x) SkIntToFixed(x)
#define GrFixedToTextScalar(x) (x)
#elif GR_TEXT_SCALAR_IS_FLOAT
typedef float GrTextScalar;
#define GrIntToTextScalar(x) ((GrTextScalar)x)
- #define GrFixedToTextScalar(x) GrFixedToFloat(x)
+ #define GrFixedToTextScalar(x) SkFixedToFloat(x)
#else
#error "Text scalar type not defined"
#endif
diff --git a/src/gpu/GrInOrderDrawBuffer.cpp b/src/gpu/GrInOrderDrawBuffer.cpp
index 2d1a0efb94..d51dca4fde 100644
--- a/src/gpu/GrInOrderDrawBuffer.cpp
+++ b/src/gpu/GrInOrderDrawBuffer.cpp
@@ -75,9 +75,9 @@ void GrInOrderDrawBuffer::resetDrawTracking() {
}
void GrInOrderDrawBuffer::drawRect(const GrRect& rect,
- const GrMatrix* matrix,
+ const SkMatrix* matrix,
const GrRect* srcRects[],
- const GrMatrix* srcMatrices[]) {
+ const SkMatrix* srcMatrices[]) {
GrAssert(!(NULL == fQuadIndexBuffer && fCurrQuad));
GrAssert(!(fDraws.empty() && fCurrQuad));
@@ -126,11 +126,11 @@ void GrInOrderDrawBuffer::drawRect(const GrRect& rect,
GrPrintf("Failed to get space for vertices!\n");
return;
}
- GrMatrix combinedMatrix = drawState->getViewMatrix();
+ SkMatrix combinedMatrix = drawState->getViewMatrix();
// We go to device space so that matrix changes allow us to concat
// rect draws. When the caller has provided explicit source rects
- // then we don't want to modify the sampler matrices. Otherwise
- // we have to account for the view matrix change in the sampler
+ // then we don't want to modify the stages' matrices. Otherwise
+ // we have to account for the view matrix change in the stage
// matrices.
uint32_t explicitCoordMask = 0;
if (srcRects) {
@@ -182,16 +182,16 @@ void GrInOrderDrawBuffer::drawRect(const GrRect& rect,
// conservative test fails.
const GrRenderTarget* target = drawState->getRenderTarget();
if (0 >= devClipRect.fLeft) {
- devClipRect.fLeft = GR_ScalarMin;
+ devClipRect.fLeft = SK_ScalarMin;
}
if (target->width() <= devClipRect.fRight) {
- devClipRect.fRight = GR_ScalarMax;
+ devClipRect.fRight = SK_ScalarMax;
}
if (0 >= devClipRect.top()) {
- devClipRect.fTop = GR_ScalarMin;
+ devClipRect.fTop = SK_ScalarMin;
}
if (target->height() <= devClipRect.fBottom) {
- devClipRect.fBottom = GR_ScalarMax;
+ devClipRect.fBottom = SK_ScalarMax;
}
int stride = VertexSize(layout);
bool insideClip = true;
@@ -300,16 +300,16 @@ void GrInOrderDrawBuffer::drawIndexedInstances(GrPrimitiveType type,
draw->fVertexBuffer != vertexBuffer) {
draw = this->recordDraw();
- draw->fIndexBuffer = geomSrc.fIndexBuffer;
- geomSrc.fIndexBuffer->ref();
- draw->fVertexBuffer = vertexBuffer;
- vertexBuffer->ref();
draw->fPrimitiveType = type;
- draw->fStartIndex = 0;
- draw->fIndexCount = 0;
draw->fStartVertex = poolState.fPoolStartVertex;
+ draw->fStartIndex = 0;
draw->fVertexCount = 0;
+ draw->fIndexCount = 0;
draw->fVertexLayout = geomSrc.fVertexLayout;
+ draw->fVertexBuffer = vertexBuffer;
+ vertexBuffer->ref();
+ draw->fIndexBuffer = geomSrc.fIndexBuffer;
+ geomSrc.fIndexBuffer->ref();
} else {
GrAssert(!(draw->fIndexCount % indicesPerInstance));
GrAssert(!(draw->fVertexCount % verticesPerInstance));
@@ -343,15 +343,16 @@ void GrInOrderDrawBuffer::drawIndexedInstances(GrPrimitiveType type,
if (!instancesToConcat) {
int startVertex = draw->fStartVertex + draw->fVertexCount;
draw = this->recordDraw();
- draw->fIndexBuffer = geomSrc.fIndexBuffer;
- geomSrc.fIndexBuffer->ref();
- draw->fVertexBuffer = vertexBuffer;
- vertexBuffer->ref();
draw->fPrimitiveType = type;
- draw->fStartIndex = 0;
draw->fStartVertex = startVertex;
+ draw->fStartIndex = 0;
draw->fVertexCount = 0;
+ draw->fIndexCount = 0;
draw->fVertexLayout = geomSrc.fVertexLayout;
+ draw->fVertexBuffer = vertexBuffer;
+ vertexBuffer->ref();
+ draw->fIndexBuffer = geomSrc.fIndexBuffer;
+ geomSrc.fIndexBuffer->ref();
instancesToConcat = maxInstancesPerDraw;
}
draw->fVertexCount += instancesToConcat * verticesPerInstance;
@@ -487,7 +488,10 @@ void GrInOrderDrawBuffer::onDrawNonIndexed(GrPrimitiveType primitiveType,
draw->fIndexBuffer = NULL;
}
-void GrInOrderDrawBuffer::onStencilPath(const GrPath* path, GrPathFill fill) {
+GrInOrderDrawBuffer::StencilPath::StencilPath() : fStroke(SkStrokeRec::kFill_InitStyle) {}
+
+void GrInOrderDrawBuffer::onStencilPath(const GrPath* path, const SkStrokeRec& stroke,
+ SkPath::FillType fill) {
if (this->needsNewClip()) {
this->recordClip();
}
@@ -499,6 +503,7 @@ void GrInOrderDrawBuffer::onStencilPath(const GrPath* path, GrPathFill fill) {
sp->fPath.reset(path);
path->ref();
sp->fFill = fill;
+ sp->fStroke = stroke;
}
void GrInOrderDrawBuffer::clear(const GrIRect* rect,
@@ -610,7 +615,7 @@ bool GrInOrderDrawBuffer::playback(GrDrawTarget* target) {
}
case kStencilPath_Cmd: {
const StencilPath& sp = fStencilPaths[currStencilPath];
- target->stencilPath(sp.fPath.get(), sp.fFill);
+ target->stencilPath(sp.fPath.get(), sp.fStroke, sp.fFill);
++currStencilPath;
break;
}
diff --git a/src/gpu/GrInOrderDrawBuffer.h b/src/gpu/GrInOrderDrawBuffer.h
index 08dd774c27..17271f8c84 100644
--- a/src/gpu/GrInOrderDrawBuffer.h
+++ b/src/gpu/GrInOrderDrawBuffer.h
@@ -17,6 +17,7 @@
#include "GrPath.h"
#include "SkClipStack.h"
+#include "SkStrokeRec.h"
#include "SkTemplates.h"
class GrGpu;
@@ -114,9 +115,9 @@ public:
// overrides from GrDrawTarget
virtual void drawRect(const GrRect& rect,
- const GrMatrix* matrix = NULL,
+ const SkMatrix* matrix = NULL,
const GrRect* srcRects[] = NULL,
- const GrMatrix* srcMatrices[] = NULL) SK_OVERRIDE;
+ const SkMatrix* srcMatrices[] = NULL) SK_OVERRIDE;
virtual void drawIndexedInstances(GrPrimitiveType type,
int instanceCount,
@@ -157,8 +158,11 @@ private:
};
struct StencilPath {
+ StencilPath();
+
SkAutoTUnref<const GrPath> fPath;
- GrPathFill fFill;
+ SkStrokeRec fStroke;
+ SkPath::FillType fFill;
};
struct Clear {
@@ -179,7 +183,7 @@ private:
virtual void onDrawNonIndexed(GrPrimitiveType primitiveType,
int startVertex,
int vertexCount) SK_OVERRIDE;
- virtual void onStencilPath(const GrPath*, GrPathFill) SK_OVERRIDE;
+ virtual void onStencilPath(const GrPath*, const SkStrokeRec& stroke, SkPath::FillType) SK_OVERRIDE;
virtual bool onReserveVertexSpace(GrVertexLayout layout,
int vertexCount,
void** vertices) SK_OVERRIDE;
diff --git a/src/gpu/GrMatrix.cpp b/src/gpu/GrMatrix.cpp
deleted file mode 100644
index e71636b366..0000000000
--- a/src/gpu/GrMatrix.cpp
+++ /dev/null
@@ -1,713 +0,0 @@
-
-/*
- * Copyright 2010 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-
-
-#include "GrMatrix.h"
-#include "GrRect.h"
-#include <stddef.h>
-
-#if 0
-#if GR_SCALAR_IS_FLOAT
- const GrScalar GrMatrix::gRESCALE(GR_Scalar1);
-#else
- GR_STATIC_ASSERT(GR_SCALAR_IS_FIXED);
- // fixed point isn't supported right now
- GR_STATIC_ASSERT(false);
-const GrScalar GrMatrix::gRESCALE(1 << 30);
-#endif
-
-const GrMatrix::MapProc GrMatrix::gMapProcs[] = {
-// Scales are not both zero
- &GrMatrix::mapIdentity,
- &GrMatrix::mapScale,
- &GrMatrix::mapTranslate,
- &GrMatrix::mapScaleAndTranslate,
- &GrMatrix::mapSkew,
- &GrMatrix::mapScaleAndSkew,
- &GrMatrix::mapSkewAndTranslate,
- &GrMatrix::mapNonPerspective,
- // no optimizations for perspective matrices
- &GrMatrix::mapPerspective,
- &GrMatrix::mapPerspective,
- &GrMatrix::mapPerspective,
- &GrMatrix::mapPerspective,
- &GrMatrix::mapPerspective,
- &GrMatrix::mapPerspective,
- &GrMatrix::mapPerspective,
- &GrMatrix::mapPerspective,
-
-// Scales are zero (every other is invalid because kScale_TypeBit must be set if
-// kZeroScale_TypeBit is set)
- &GrMatrix::mapInvalid,
- &GrMatrix::mapZero,
- &GrMatrix::mapInvalid,
- &GrMatrix::mapSetToTranslate,
- &GrMatrix::mapInvalid,
- &GrMatrix::mapSwappedScale,
- &GrMatrix::mapInvalid,
- &GrMatrix::mapSwappedScaleAndTranslate,
-
- // no optimizations for perspective matrices
- &GrMatrix::mapInvalid,
- &GrMatrix::mapZero,
- &GrMatrix::mapInvalid,
- &GrMatrix::mapPerspective,
- &GrMatrix::mapInvalid,
- &GrMatrix::mapPerspective,
- &GrMatrix::mapInvalid,
- &GrMatrix::mapPerspective,
-};
-
-void GrMatrix::setIdentity() {
- fM[0] = GR_Scalar1; fM[1] = 0; fM[2] = 0;
- fM[3] = 0; fM[4] = GR_Scalar1; fM[5] = 0;
- fM[6] = 0; fM[7] = 0; fM[8] = gRESCALE;
- fTypeMask = 0;
-}
-
-void GrMatrix::setTranslate(GrScalar dx, GrScalar dy) {
- fM[0] = GR_Scalar1; fM[1] = 0; fM[2] = dx;
- fM[3] = 0; fM[4] = GR_Scalar1; fM[5] = dy;
- fM[6] = 0; fM[7] = 0; fM[8] = gRESCALE;
- fTypeMask = (0 != dx || 0 != dy) ? kTranslate_TypeBit : 0;
-}
-
-void GrMatrix::setScale(GrScalar sx, GrScalar sy) {
- fM[0] = sx; fM[1] = 0; fM[2] = 0;
- fM[3] = 0; fM[4] = sy; fM[5] = 0;
- fM[6] = 0; fM[7] = 0; fM[8] = gRESCALE;
- fTypeMask = (GR_Scalar1 != sx || GR_Scalar1 != sy) ? kScale_TypeBit : 0;
-}
-
-void GrMatrix::setSkew(GrScalar skx, GrScalar sky) {
- fM[0] = GR_Scalar1; fM[1] = skx; fM[2] = 0;
- fM[3] = sky; fM[4] = GR_Scalar1; fM[5] = 0;
- fM[6] = 0; fM[7] = 0; fM[8] = gRESCALE;
- fTypeMask = (0 != skx || 0 != sky) ? kSkew_TypeBit : 0;
-}
-
-void GrMatrix::setConcat(const GrMatrix& a, const GrMatrix& b) {
- if (a.isIdentity()) {
- if (this != &b) {
- for (int i = 0; i < 9; ++i) {
- fM[i] = b.fM[i];
- }
- fTypeMask = b.fTypeMask;
- }
- return;
- }
-
- if (b.isIdentity()) {
- GrAssert(!a.isIdentity());
- if (this != &a) {
- for (int i = 0; i < 9; ++i) {
- fM[i] = a.fM[i];
- }
- fTypeMask = a.fTypeMask;
- }
- return;
- }
-
- // a and/or b could be this
- GrMatrix tmp;
-
- // could do more optimizations based on type bits. Hopefully this call is
- // low frequency.
- // TODO: make this work for fixed point
- if (!((b.fTypeMask | a.fTypeMask) & kPerspective_TypeBit)) {
- tmp.fM[0] = a.fM[0] * b.fM[0] + a.fM[1] * b.fM[3];
- tmp.fM[1] = a.fM[0] * b.fM[1] + a.fM[1] * b.fM[4];
- tmp.fM[2] = a.fM[0] * b.fM[2] + a.fM[1] * b.fM[5] + a.fM[2] * gRESCALE;
-
- tmp.fM[3] = a.fM[3] * b.fM[0] + a.fM[4] * b.fM[3];
- tmp.fM[4] = a.fM[3] * b.fM[1] + a.fM[4] * b.fM[4];
- tmp.fM[5] = a.fM[3] * b.fM[2] + a.fM[4] * b.fM[5] + a.fM[5] * gRESCALE;
-
- tmp.fM[6] = 0;
- tmp.fM[7] = 0;
- tmp.fM[8] = gRESCALE * gRESCALE;
- } else {
- tmp.fM[0] = a.fM[0] * b.fM[0] + a.fM[1] * b.fM[3] + a.fM[2] * b.fM[6];
- tmp.fM[1] = a.fM[0] * b.fM[1] + a.fM[1] * b.fM[4] + a.fM[2] * b.fM[7];
- tmp.fM[2] = a.fM[0] * b.fM[2] + a.fM[1] * b.fM[5] + a.fM[2] * b.fM[8];
-
- tmp.fM[3] = a.fM[3] * b.fM[0] + a.fM[4] * b.fM[3] + a.fM[5] * b.fM[6];
- tmp.fM[4] = a.fM[3] * b.fM[1] + a.fM[4] * b.fM[4] + a.fM[5] * b.fM[7];
- tmp.fM[5] = a.fM[3] * b.fM[2] + a.fM[4] * b.fM[5] + a.fM[5] * b.fM[8];
-
- tmp.fM[6] = a.fM[6] * b.fM[0] + a.fM[7] * b.fM[3] + a.fM[8] * b.fM[6];
- tmp.fM[7] = a.fM[6] * b.fM[1] + a.fM[7] * b.fM[4] + a.fM[8] * b.fM[7];
- tmp.fM[8] = a.fM[6] * b.fM[2] + a.fM[7] * b.fM[5] + a.fM[8] * b.fM[8];
- }
- *this = tmp;
- this->computeTypeMask();
-}
-
-void GrMatrix::preConcat(const GrMatrix& m) {
- setConcat(*this, m);
-}
-
-void GrMatrix::postConcat(const GrMatrix& m) {
- setConcat(m, *this);
-}
-
-double GrMatrix::determinant() const {
- if (fTypeMask & kPerspective_TypeBit) {
- return fM[0]*((double)fM[4]*fM[8] - (double)fM[5]*fM[7]) +
- fM[1]*((double)fM[5]*fM[6] - (double)fM[3]*fM[8]) +
- fM[2]*((double)fM[3]*fM[7] - (double)fM[4]*fM[6]);
- } else {
- return (double)fM[0]*fM[4]*gRESCALE -
- (double)fM[1]*fM[3]*gRESCALE;
- }
-}
-
-bool GrMatrix::invert(GrMatrix* inverted) const {
-
- if (isIdentity()) {
- if (inverted != this) {
- inverted->setIdentity();
- }
- return true;
- }
- static const double MIN_DETERMINANT_SQUARED = 1.e-16;
-
- // could do more optimizations based on type bits. Hopefully this call is
- // low frequency.
-
- double det = determinant();
-
- // check if we can't be inverted
- if (det*det <= MIN_DETERMINANT_SQUARED) {
- return false;
- } else if (NULL == inverted) {
- return true;
- }
-
- double t[9];
-
- if (fTypeMask & kPerspective_TypeBit) {
- t[0] = ((double)fM[4]*fM[8] - (double)fM[5]*fM[7]);
- t[1] = ((double)fM[2]*fM[7] - (double)fM[1]*fM[8]);
- t[2] = ((double)fM[1]*fM[5] - (double)fM[2]*fM[4]);
- t[3] = ((double)fM[5]*fM[6] - (double)fM[3]*fM[8]);
- t[4] = ((double)fM[0]*fM[8] - (double)fM[2]*fM[6]);
- t[5] = ((double)fM[2]*fM[3] - (double)fM[0]*fM[5]);
- t[6] = ((double)fM[3]*fM[7] - (double)fM[4]*fM[6]);
- t[7] = ((double)fM[1]*fM[6] - (double)fM[0]*fM[7]);
- t[8] = ((double)fM[0]*fM[4] - (double)fM[1]*fM[3]);
- det = 1.0 / det;
- for (int i = 0; i < 9; ++i) {
- inverted->fM[i] = (GrScalar)(t[i] * det);
- }
- } else {
- t[0] = (double)fM[4]*gRESCALE;
- t[1] = -(double)fM[1]*gRESCALE;
- t[2] = (double)fM[1]*fM[5] - (double)fM[2]*fM[4];
- t[3] = -(double)fM[3]*gRESCALE;
- t[4] = (double)fM[0]*gRESCALE;
- t[5] = (double)fM[2]*fM[3] - (double)fM[0]*fM[5];
- //t[6] = 0.0;
- //t[7] = 0.0;
- t[8] = (double)fM[0]*fM[4] - (double)fM[1]*fM[3];
- det = 1.0 / det;
- for (int i = 0; i < 6; ++i) {
- inverted->fM[i] = (GrScalar)(t[i] * det);
- }
- inverted->fM[6] = 0;
- inverted->fM[7] = 0;
- inverted->fM[8] = (GrScalar)(t[8] * det);
- }
- inverted->computeTypeMask();
- return true;
-}
-
-void GrMatrix::mapRect(GrRect* dst, const GrRect& src) const {
- GrPoint srcPts[4], dstPts[4];
- srcPts[0].set(src.fLeft, src.fTop);
- srcPts[1].set(src.fRight, src.fTop);
- srcPts[2].set(src.fRight, src.fBottom);
- srcPts[3].set(src.fLeft, src.fBottom);
- this->mapPoints(dstPts, srcPts, 4);
- dst->setBounds(dstPts, 4);
-}
-
-bool GrMatrix::hasPerspective() const {
- GrAssert(!!(kPerspective_TypeBit & fTypeMask) ==
- (fM[kPersp0] != 0 || fM[kPersp1] != 0 || fM[kPersp2] != gRESCALE));
- return 0 != (kPerspective_TypeBit & fTypeMask);
-}
-
-bool GrMatrix::isIdentity() const {
- GrAssert((0 == fTypeMask) ==
- (GR_Scalar1 == fM[kScaleX] && 0 == fM[kSkewX] && 0 == fM[kTransX] &&
- 0 == fM[kSkewY] && GR_Scalar1 == fM[kScaleY] && 0 == fM[kTransY] &&
- 0 == fM[kPersp0] && 0 == fM[kPersp1] && gRESCALE == fM[kPersp2]));
- return (0 == fTypeMask);
-}
-
-
-bool GrMatrix::preservesAxisAlignment() const {
-
- // check if matrix is trans and scale only
- static const int gAllowedMask1 = kScale_TypeBit | kTranslate_TypeBit;
-
- if (!(~gAllowedMask1 & fTypeMask)) {
- return true;
- }
-
- // check matrix is trans and skew only (0 scale)
- static const int gAllowedMask2 = kScale_TypeBit | kSkew_TypeBit |
- kTranslate_TypeBit | kZeroScale_TypeBit;
-
- if (!(~gAllowedMask2 & fTypeMask) && (kZeroScale_TypeBit & fTypeMask)) {
- return true;
- }
-
- return false;
-}
-
-GrScalar GrMatrix::getMaxStretch() const {
-
- if (fTypeMask & kPerspective_TypeBit) {
- return -GR_Scalar1;
- }
-
- GrScalar stretch;
-
- if (isIdentity()) {
- stretch = GR_Scalar1;
- } else if (!(fTypeMask & kSkew_TypeBit)) {
- stretch = GrMax(GrScalarAbs(fM[kScaleX]), GrScalarAbs(fM[kScaleY]));
- } else if (fTypeMask & kZeroScale_TypeBit) {
- stretch = GrMax(GrScalarAbs(fM[kSkewX]), GrScalarAbs(fM[kSkewY]));
- } else {
- // ignore the translation part of the matrix, just look at 2x2 portion.
- // compute singular values, take largest abs value.
- // [a b; b c] = A^T*A
- GrScalar a = GrMul(fM[kScaleX], fM[kScaleX]) + GrMul(fM[kSkewY], fM[kSkewY]);
- GrScalar b = GrMul(fM[kScaleX], fM[kSkewX]) + GrMul(fM[kScaleY], fM[kSkewY]);
- GrScalar c = GrMul(fM[kSkewX], fM[kSkewX]) + GrMul(fM[kScaleY], fM[kScaleY]);
- // eigenvalues of A^T*A are the squared singular values of A.
- // characteristic equation is det((A^T*A) - l*I) = 0
- // l^2 - (a + c)l + (ac-b^2)
- // solve using quadratic equation (divisor is non-zero since l^2 has 1 coeff
- // and roots are guaraunteed to be pos and real).
- GrScalar largerRoot;
- GrScalar bSqd = GrMul(b,b);
- // TODO: fixed point tolerance value.
- if (bSqd < 1e-10) { // will be true if upper left 2x2 is orthogonal, which is common, so save some math
- largerRoot = GrMax(a, c);
- } else {
- GrScalar aminusc = a - c;
- GrScalar apluscdiv2 = (a + c) / 2;
- GrScalar x = sqrtf(GrMul(aminusc,aminusc) + GrMul(4,(bSqd))) / 2;
- largerRoot = apluscdiv2 + x;
- }
-
- stretch = sqrtf(largerRoot);
- }
-#if GR_DEBUG && 0
- // test a bunch of vectors. None should be scaled by more than stretch
- // (modulo some error) and we should find a vector that is scaled by almost
- // stretch.
- GrPoint pt;
- GrScalar max = 0;
- for (int i = 0; i < 1000; ++i) {
- GrScalar x = (float)rand() / RAND_MAX;
- GrScalar y = sqrtf(1 - (x*x));
- pt.fX = fM[kScaleX]*x + fM[kSkewX]*y;
- pt.fY = fM[kSkewY]*x + fM[kScaleY]*y;
- GrScalar d = pt.distanceToOrigin();
- GrAssert(d <= (1.0001 * stretch));
- max = GrMax(max, pt.distanceToOrigin());
- }
- GrAssert((stretch - max) < .05*stretch);
-#endif
- return stretch;
-}
-
-bool GrMatrix::operator == (const GrMatrix& m) const {
- if (fTypeMask != m.fTypeMask) {
- return false;
- }
- if (!fTypeMask) {
- return true;
- }
- for (int i = 0; i < 9; ++i) {
- if (m.fM[i] != fM[i]) {
- return false;
- }
- }
- return true;
-}
-
-bool GrMatrix::operator != (const GrMatrix& m) const {
- return !(*this == m);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Matrix transformation procs
-//////
-
-void GrMatrix::mapIdentity(GrPoint* dst, const GrPoint* src, uint32_t count) const {
- if (src != dst) {
- for (uint32_t i = 0; i < count; ++i) {
- dst[i] = src[i];
- }
- }
-}
-
-void GrMatrix::mapScale(GrPoint* dst, const GrPoint* src, uint32_t count) const {
- for (uint32_t i = 0; i < count; ++i) {
- dst[i].fX = GrMul(src[i].fX, fM[kScaleX]);
- dst[i].fY = GrMul(src[i].fY, fM[kScaleY]);
- }
-}
-
-
-void GrMatrix::mapTranslate(GrPoint* dst, const GrPoint* src, uint32_t count) const {
- for (uint32_t i = 0; i < count; ++i) {
- dst[i].fX = src[i].fX + fM[kTransX];
- dst[i].fY = src[i].fY + fM[kTransY];
- }
-}
-
-void GrMatrix::mapScaleAndTranslate(GrPoint* dst, const GrPoint* src, uint32_t count) const {
- for (uint32_t i = 0; i < count; ++i) {
- dst[i].fX = GrMul(src[i].fX, fM[kScaleX]) + fM[kTransX];
- dst[i].fY = GrMul(src[i].fY, fM[kScaleY]) + fM[kTransY];
- }
-}
-
-void GrMatrix::mapSkew(GrPoint* dst, const GrPoint* src, uint32_t count) const {
- if (src != dst) {
- for (uint32_t i = 0; i < count; ++i) {
- dst[i].fX = src[i].fX + GrMul(src[i].fY, fM[kSkewX]);
- dst[i].fY = src[i].fY + GrMul(src[i].fX, fM[kSkewY]);
- }
- } else {
- for (uint32_t i = 0; i < count; ++i) {
- GrScalar newX = src[i].fX + GrMul(src[i].fY, fM[kSkewX]);
- dst[i].fY = src[i].fY + GrMul(src[i].fX, fM[kSkewY]);
- dst[i].fX = newX;
- }
- }
-}
-
-void GrMatrix::mapScaleAndSkew(GrPoint* dst, const GrPoint* src, uint32_t count) const {
- if (src != dst) {
- for (uint32_t i = 0; i < count; ++i) {
- dst[i].fX = GrMul(src[i].fX, fM[kScaleX]) + GrMul(src[i].fY, fM[kSkewX]);
- dst[i].fY = GrMul(src[i].fY, fM[kScaleY]) + GrMul(src[i].fX, fM[kSkewY]);
- }
- } else {
- for (uint32_t i = 0; i < count; ++i) {
- GrScalar newX = GrMul(src[i].fX, fM[kScaleX]) + GrMul(src[i].fY, fM[kSkewX]);
- dst[i].fY = GrMul(src[i].fY, fM[kScaleY]) + GrMul(src[i].fX, fM[kSkewY]);
- dst[i].fX = newX;
- }
- }
-}
-
-void GrMatrix::mapSkewAndTranslate(GrPoint* dst, const GrPoint* src, uint32_t count) const {
- if (src != dst) {
- for (uint32_t i = 0; i < count; ++i) {
- dst[i].fX = src[i].fX + GrMul(src[i].fY, fM[kSkewX]) + fM[kTransX];
- dst[i].fY = src[i].fY + GrMul(src[i].fX, fM[kSkewY]) + fM[kTransY];
- }
- } else {
- for (uint32_t i = 0; i < count; ++i) {
- GrScalar newX = src[i].fX + GrMul(src[i].fY, fM[kSkewX]) + fM[kTransX];
- dst[i].fY = src[i].fY + GrMul(src[i].fX, fM[kSkewY]) + fM[kTransY];
- dst[i].fX = newX;
- }
- }
-}
-
-void GrMatrix::mapNonPerspective(GrPoint* dst, const GrPoint* src, uint32_t count) const {
- if (src != dst) {
- for (uint32_t i = 0; i < count; ++i) {
- dst[i].fX = GrMul(fM[kScaleX], src[i].fX) + GrMul(fM[kSkewX], src[i].fY) + fM[kTransX];
- dst[i].fY = GrMul(fM[kSkewY], src[i].fX) + GrMul(fM[kScaleY], src[i].fY) + fM[kTransY];
- }
- } else {
- for (uint32_t i = 0; i < count; ++i) {
- GrScalar newX = GrMul(fM[kScaleX], src[i].fX) + GrMul(fM[kSkewX], src[i].fY) + fM[kTransX];
- dst[i].fY = GrMul(fM[kSkewY], src[i].fX) + GrMul(fM[kScaleY], src[i].fY) + fM[kTransY];
- dst[i].fX = newX;
- }
- }
-}
-
-void GrMatrix::mapPerspective(GrPoint* dst, const GrPoint* src, uint32_t count) const {
- for (uint32_t i = 0; i < count; ++i) {
- GrScalar x, y, w;
- x = GrMul(fM[kScaleX], src[i].fX) + GrMul(fM[kSkewX], src[i].fY) + fM[kTransX];
- y = GrMul(fM[kSkewY], src[i].fX) + GrMul(fM[kScaleY], src[i].fY) + fM[kTransY];
- w = GrMul(fM[kPersp0], src[i].fX) + GrMul(fM[kPersp1], src[i].fY) + fM[kPersp2];
- // TODO need fixed point invert
- if (w) {
- w = 1 / w;
- }
- dst[i].fX = GrMul(x, w);
- dst[i].fY = GrMul(y, w);
- }
-}
-
-void GrMatrix::mapInvalid(GrPoint* dst, const GrPoint* src, uint32_t count) const {
- GrAssert(0);
-}
-
-void GrMatrix::mapZero(GrPoint* dst, const GrPoint* src, uint32_t count) const {
- memset(dst, 0, sizeof(GrPoint)*count);
-}
-
-void GrMatrix::mapSetToTranslate(GrPoint* dst, const GrPoint* src, uint32_t count) const {
- for (uint32_t i = 0; i < count; ++i) {
- dst[i].fX = fM[kTransX];
- dst[i].fY = fM[kTransY];
- }
-}
-
-void GrMatrix::mapSwappedScale(GrPoint* dst, const GrPoint* src, uint32_t count) const {
- if (src != dst) {
- for (uint32_t i = 0; i < count; ++i) {
- dst[i].fX = GrMul(src[i].fY, fM[kSkewX]);
- dst[i].fY = GrMul(src[i].fX, fM[kSkewY]);
- }
- } else {
- for (uint32_t i = 0; i < count; ++i) {
- GrScalar newX = GrMul(src[i].fY, fM[kSkewX]);
- dst[i].fY = GrMul(src[i].fX, fM[kSkewY]);
- dst[i].fX = newX;
- }
- }
-}
-
-void GrMatrix::mapSwappedScaleAndTranslate(GrPoint* dst, const GrPoint* src, uint32_t count) const {
- if (src != dst) {
- for (uint32_t i = 0; i < count; ++i) {
- dst[i].fX = GrMul(src[i].fY, fM[kSkewX]) + fM[kTransX];
- dst[i].fY = GrMul(src[i].fX, fM[kSkewY]) + fM[kTransY];
- }
- } else {
- for (uint32_t i = 0; i < count; ++i) {
- GrScalar newX = GrMul(src[i].fY, fM[kSkewX]) + fM[kTransX];
- dst[i].fY = GrMul(src[i].fX, fM[kSkewY]) + fM[kTransY];
- dst[i].fX = newX;
- }
- }
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Unit test
-//////
-
-#include "GrRandom.h"
-
-#if GR_DEBUG
-enum MatrixType {
- kRotate_MatrixType,
- kScaleX_MatrixType,
- kScaleY_MatrixType,
- kSkewX_MatrixType,
- kSkewY_MatrixType,
- kTranslateX_MatrixType,
- kTranslateY_MatrixType,
- kSwapScaleXY_MatrixType,
- kPersp_MatrixType,
-
- kMatrixTypeCount
-};
-
-static void create_matrix(GrMatrix* matrix, GrRandom& rand) {
- MatrixType type = (MatrixType)(rand.nextU() % kMatrixTypeCount);
- switch (type) {
- case kRotate_MatrixType: {
- float angle = rand.nextF() * 2 *3.14159265358979323846f;
- GrScalar cosa = GrFloatToScalar(cosf(angle));
- GrScalar sina = GrFloatToScalar(sinf(angle));
- matrix->setAll(cosa, -sina, 0,
- sina, cosa, 0,
- 0, 0, GrMatrix::I()[8]);
- } break;
- case kScaleX_MatrixType: {
- GrScalar scale = GrFloatToScalar(rand.nextF(-2, 2));
- matrix->setAll(scale, 0, 0,
- 0, GR_Scalar1, 0,
- 0, 0, GrMatrix::I()[8]);
- } break;
- case kScaleY_MatrixType: {
- GrScalar scale = GrFloatToScalar(rand.nextF(-2, 2));
- matrix->setAll(GR_Scalar1, 0, 0,
- 0, scale, 0,
- 0, 0, GrMatrix::I()[8]);
- } break;
- case kSkewX_MatrixType: {
- GrScalar skew = GrFloatToScalar(rand.nextF(-2, 2));
- matrix->setAll(GR_Scalar1, skew, 0,
- 0, GR_Scalar1, 0,
- 0, 0, GrMatrix::I()[8]);
- } break;
- case kSkewY_MatrixType: {
- GrScalar skew = GrFloatToScalar(rand.nextF(-2, 2));
- matrix->setAll(GR_Scalar1, 0, 0,
- skew, GR_Scalar1, 0,
- 0, 0, GrMatrix::I()[8]);
- } break;
- case kTranslateX_MatrixType: {
- GrScalar trans = GrFloatToScalar(rand.nextF(-10, 10));
- matrix->setAll(GR_Scalar1, 0, trans,
- 0, GR_Scalar1, 0,
- 0, 0, GrMatrix::I()[8]);
- } break;
- case kTranslateY_MatrixType: {
- GrScalar trans = GrFloatToScalar(rand.nextF(-10, 10));
- matrix->setAll(GR_Scalar1, 0, 0,
- 0, GR_Scalar1, trans,
- 0, 0, GrMatrix::I()[8]);
- } break;
- case kSwapScaleXY_MatrixType: {
- GrScalar xy = GrFloatToScalar(rand.nextF(-2, 2));
- GrScalar yx = GrFloatToScalar(rand.nextF(-2, 2));
- matrix->setAll(0, xy, 0,
- yx, 0, 0,
- 0, 0, GrMatrix::I()[8]);
- } break;
- case kPersp_MatrixType: {
- GrScalar p0 = GrFloatToScalar(rand.nextF(-2, 2));
- GrScalar p1 = GrFloatToScalar(rand.nextF(-2, 2));
- GrScalar p2 = GrFloatToScalar(rand.nextF(-0.5f, 0.75f));
- matrix->setAll(GR_Scalar1, 0, 0,
- 0, GR_Scalar1, 0,
- p0, p1, GrMul(p2,GrMatrix::I()[8]));
- } break;
- default:
- GrAssert(0);
- break;
- }
-}
-#endif
-
-void GrMatrix::UnitTest() {
- GrRandom rand;
-
- // Create a bunch of matrices and test point mapping, max stretch calc,
- // inversion and multiply-by-inverse.
-#if GR_DEBUG
- for (int i = 0; i < 10000; ++i) {
- GrMatrix a, b;
- a.setIdentity();
- int num = rand.nextU() % 6;
- // force testing of I and swapXY
- if (0 == i) {
- num = 0;
- GrAssert(a.isIdentity());
- } else if (1 == i) {
- num = 0;
- a.setAll(0, GR_Scalar1, 0,
- GR_Scalar1, 0, 0,
- 0, 0, I()[8]);
- }
- for (int j = 0; j < num; ++j) {
- create_matrix(&b, rand);
- a.preConcat(b);
- }
-
- GrScalar maxStretch = a.getMaxStretch();
- if (maxStretch > 0) {
- maxStretch = GrMul(GR_Scalar1 + GR_Scalar1 / 100, maxStretch);
- }
- GrPoint origin = a.mapPoint(GrPoint::Make(0,0));
-
- for (int j = 0; j < 9; ++j) {
- int mask, origMask = a.fTypeMask;
- GrScalar old = a[j];
-
- a.set(j, GR_Scalar1);
- mask = a.fTypeMask;
- a.computeTypeMask();
- GrAssert(mask == a.fTypeMask);
-
- a.set(j, 0);
- mask = a.fTypeMask;
- a.computeTypeMask();
- GrAssert(mask == a.fTypeMask);
-
- a.set(j, 10 * GR_Scalar1);
- mask = a.fTypeMask;
- a.computeTypeMask();
- GrAssert(mask == a.fTypeMask);
-
- a.set(j, old);
- GrAssert(a.fTypeMask == origMask);
- }
-
- for (int j = 0; j < 100; ++j) {
- GrPoint pt;
- pt.fX = GrFloatToScalar(rand.nextF(-10, 10));
- pt.fY = GrFloatToScalar(rand.nextF(-10, 10));
-
- GrPoint t0, t1, t2;
- t0 = a.mapPoint(pt); // map to a new point
- t1 = pt;
- a.mapPoints(&t1, &t1, 1); // in place
- a.mapPerspective(&t2, &pt, 1); // full mult
- GrAssert(t0 == t1 && t1 == t2);
- if (maxStretch >= 0.f) {
- GrVec vec = origin - t0;
-// vec.setBetween(t0, origin);
- GrScalar stretch = vec.length() / pt.distanceToOrigin();
- GrAssert(stretch <= maxStretch);
- }
- }
- double det = a.determinant();
- if (fabs(det) > 1e-3 && a.invert(&b)) {
- GrMatrix c;
- c.setConcat(a,b);
- for (int i = 0; i < 9; ++i) {
- GrScalar diff = GrScalarAbs(c[i] - I()[i]);
- GrAssert(diff < (5*GR_Scalar1 / 100));
- }
- }
- }
-#endif
-}
-
-///////////////////////////////////////////////////////////////////////////////
-#endif
-
-int Gr_clz(uint32_t n) {
- if (0 == n) {
- return 32;
- }
-
- int count = 0;
- if (0 == (n & 0xFFFF0000)) {
- count += 16;
- n <<= 16;
- }
- if (0 == (n & 0xFF000000)) {
- count += 8;
- n <<= 8;
- }
- if (0 == (n & 0xF0000000)) {
- count += 4;
- n <<= 4;
- }
- if (0 == (n & 0xC0000000)) {
- count += 2;
- n <<= 2;
- }
- if (0 == (n & 0x80000000)) {
- count += 1;
- }
- return count;
-}
diff --git a/src/gpu/GrPathRenderer.h b/src/gpu/GrPathRenderer.h
index fc3d67289a..fa2112c410 100644
--- a/src/gpu/GrPathRenderer.h
+++ b/src/gpu/GrPathRenderer.h
@@ -12,7 +12,9 @@
#include "GrDrawTarget.h"
#include "GrPathRendererChain.h"
+#include "GrStencil.h"
+#include "SkStrokeRec.h"
#include "SkTArray.h"
class SkPath;
@@ -22,124 +24,155 @@ struct GrPoint;
/**
* Base class for drawing paths into a GrDrawTarget.
*
- * Derived classes can use stages GrPaint::kTotalStages through
- * GrDrawState::kNumStages-1. The stages before GrPaint::kTotalStages
- * are reserved for setting up the draw (i.e., textures and filter masks).
+ * Derived classes can use stages GrPaint::kTotalStages through GrDrawState::kNumStages-1. The
+ * stages before GrPaint::kTotalStages are reserved for setting up the draw (i.e., textures and
+ * filter masks).
*/
class GR_API GrPathRenderer : public GrRefCnt {
public:
SK_DECLARE_INST_COUNT(GrPathRenderer)
/**
- * This is called to install custom path renderers in every GrContext at
- * create time. The default implementation in GrCreatePathRenderer_none.cpp
- * does not add any additional renderers. Link against another
- * implementation to install your own. The first added is the most preferred
- * path renderer, second is second most preferred, etc.
+ * This is called to install custom path renderers in every GrContext at create time. The
+ * default implementation in GrCreatePathRenderer_none.cpp does not add any additional
+ * renderers. Link against another implementation to install your own. The first added is the
+ * most preferred path renderer, second is second most preferred, etc.
*
* @param context the context that will use the path renderer
- * @param flags flags indicating how path renderers will be used
* @param prChain the chain to add path renderers to.
*/
- static void AddPathRenderers(GrContext* context,
- GrPathRendererChain::UsageFlags flags,
- GrPathRendererChain* prChain);
+ static void AddPathRenderers(GrContext* context, GrPathRendererChain* prChain);
GrPathRenderer();
/**
- * For complex clips Gr uses the stencil buffer. The path renderer must be
- * able to render paths into the stencil buffer. However, the path renderer
- * itself may require the stencil buffer to resolve the path fill rule.
- * This function queries whether the path render needs its own stencil
- * pass. If this returns false then drawPath() should not modify the
- * the target's stencil settings but use those already set on target. The
- * target is passed as a param in case the answer depends upon draw state.
+ * A caller may wish to use a path renderer to draw a path into the stencil buffer. However,
+ * the path renderer itself may require use of the stencil buffer. Also a path renderer may
+ * use a GrEffect coverage stage that sets coverage to zero to eliminate pixels that are covered
+ * by bounding geometry but outside the path. These exterior pixels would still be rendered into
+ * the stencil.
*
- * @param target target that the path will be rendered to
- * @param path the path that will be drawn
- * @param fill the fill rule that will be used, will never be an inverse
- * rule.
+ * A GrPathRenderer can provide three levels of support for stenciling paths:
+ * 1) kNoRestriction: This is the most general. The caller sets up the GrDrawState on the target
+ * and calls drawPath(). The path is rendered exactly as the draw state
+ * indicates including support for simultaneous color and stenciling with
+ * arbitrary stenciling rules. Pixels partially covered by AA paths are
+ * affected by the stencil settings.
+ * 2) kStencilOnly: The path renderer cannot apply arbitrary stencil rules nor shade and stencil
+ * simultaneously. The path renderer does support the stencilPath() function
+ * which performs no color writes and writes a non-zero stencil value to pixels
+ * covered by the path.
+ * 3) kNoSupport: This path renderer cannot be used to stencil the path.
+ */
+ typedef GrPathRendererChain::StencilSupport StencilSupport;
+ static const StencilSupport kNoSupport_StencilSupport =
+ GrPathRendererChain::kNoSupport_StencilSupport;
+ static const StencilSupport kStencilOnly_StencilSupport =
+ GrPathRendererChain::kStencilOnly_StencilSupport;
+ static const StencilSupport kNoRestriction_StencilSupport =
+ GrPathRendererChain::kNoRestriction_StencilSupport;
+
+ /**
+ * This function is to get the stencil support for a particular path. The path's fill must
+ * not be an inverse type.
*
- * @return false if this path renderer can generate interior-only fragments
- * without changing the stencil settings on the target. If it
- * returns true the drawPathToStencil will be used when rendering
- * clips.
+ * @param target target that the path will be rendered to
+ * @param path the path that will be drawn
+ * @param stroke the stroke information (width, join, cap).
*/
- virtual bool requiresStencilPass(const SkPath& path,
- GrPathFill fill,
+ StencilSupport getStencilSupport(const SkPath& path,
+ const SkStrokeRec& stroke,
const GrDrawTarget* target) const {
- return false;
+ GrAssert(!path.isInverseFillType());
+ return this->onGetStencilSupport(path, stroke, target);
}
/**
- * Returns true if this path renderer is able to render the path.
- * Returning false allows the caller to fallback to another path renderer
- * This function is called when searching for a path renderer capable of
- * rendering a path.
+ * Returns true if this path renderer is able to render the path. Returning false allows the
+ * caller to fallback to another path renderer This function is called when searching for a path
+ * renderer capable of rendering a path.
*
* @param path The path to draw
- * @param fill The fill rule to use
+ * @param stroke The stroke information (width, join, cap)
* @param target The target that the path will be rendered to
* @param antiAlias True if anti-aliasing is required.
*
* @return true if the path can be drawn by this object, false otherwise.
*/
virtual bool canDrawPath(const SkPath& path,
- GrPathFill fill,
+ const SkStrokeRec& rec,
const GrDrawTarget* target,
bool antiAlias) const = 0;
/**
- * Draws the path into the draw target. If requiresStencilBuffer returned
- * false then the target may be setup for stencil rendering (since the
- * path renderer didn't claim that it needs to use the stencil internally).
+ * Draws the path into the draw target. If getStencilSupport() would return kNoRestriction then
+ * the subclass must respect the stencil settings of the target's draw state.
*
* @param path the path to draw.
- * @param fill the path filling rule to use.
+ * @param stroke the stroke information (width, join, cap)
* @param target target that the path will be rendered to
* @param antiAlias true if anti-aliasing is required.
*/
- virtual bool drawPath(const SkPath& path,
- GrPathFill fill,
- GrDrawTarget* target,
- bool antiAlias) {
- GrAssert(this->canDrawPath(path, fill, target, antiAlias));
- return this->onDrawPath(path, fill, target, antiAlias);
+ bool drawPath(const SkPath& path,
+ const SkStrokeRec& stroke,
+ GrDrawTarget* target,
+ bool antiAlias) {
+ GrAssert(this->canDrawPath(path, stroke, target, antiAlias));
+ GrAssert(target->drawState()->getStencil().isDisabled() ||
+ kNoRestriction_StencilSupport == this->getStencilSupport(path, stroke, target));
+ return this->onDrawPath(path, stroke, target, antiAlias);
}
/**
- * Draws the path to the stencil buffer. Assume the writable stencil bits
- * are already initialized to zero. Fill will always be either
- * kWinding_GrPathFill or kEvenOdd_GrPathFill.
- *
- * Only called if requiresStencilPass returns true for the same combo of
- * target, path, and fill. Never called with an inverse fill.
- *
- * The default implementation assumes the path filling algorithm doesn't
- * require a separate stencil pass and so crashes.
+ * Draws the path to the stencil buffer. Assume the writable stencil bits are already
+ * initialized to zero. The pixels inside the path will have non-zero stencil values afterwards.
*
+ * @param path the path to draw.
+ * @param stroke the stroke information (width, join, cap)
+ * @param target target that the path will be rendered to
*/
- virtual void drawPathToStencil(const SkPath& path,
- GrPathFill fill,
- GrDrawTarget* target) {
- GrCrash("Unexpected call to drawPathToStencil.");
+ void stencilPath(const SkPath& path, const SkStrokeRec& stroke, GrDrawTarget* target) {
+ GrAssert(kNoSupport_StencilSupport != this->getStencilSupport(path, stroke, target));
+ this->onStencilPath(path, stroke, target);
}
protected:
/**
- * Draws the path into the draw target.
- *
- * @param path the path to draw.
- * @param fill the path filling rule to use.
- * @param target target that the path will be rendered to
- * @param antiAlias whether antialiasing is enabled or not.
+ * Subclass overrides if it has any limitations of stenciling support.
+ */
+ virtual StencilSupport onGetStencilSupport(const SkPath&,
+ const SkStrokeRec&,
+ const GrDrawTarget*) const {
+ return kNoRestriction_StencilSupport;
+ }
+
+ /**
+ * Subclass implementation of drawPath()
*/
virtual bool onDrawPath(const SkPath& path,
- GrPathFill fill,
+ const SkStrokeRec& stroke,
GrDrawTarget* target,
bool antiAlias) = 0;
+ /**
+ * Subclass implementation of stencilPath(). Subclass must override iff it ever returns
+ * kStencilOnly in onGetStencilSupport().
+ */
+ virtual void onStencilPath(const SkPath& path, const SkStrokeRec& stroke, GrDrawTarget* target) {
+ GrDrawTarget::AutoStateRestore asr(target, GrDrawTarget::kPreserve_ASRInit);
+ GrDrawState* drawState = target->drawState();
+ GR_STATIC_CONST_SAME_STENCIL(kIncrementStencil,
+ kReplace_StencilOp,
+ kReplace_StencilOp,
+ kAlways_StencilFunc,
+ 0xffff,
+ 0xffff,
+ 0xffff);
+ drawState->setStencil(kIncrementStencil);
+ drawState->enableState(GrDrawState::kNoColorWrites_StateBit);
+ this->drawPath(path, stroke, target, false);
+ }
+
private:
typedef GrRefCnt INHERITED;
diff --git a/src/gpu/GrPathRendererChain.cpp b/src/gpu/GrPathRendererChain.cpp
index 2814f999be..23f757624b 100644
--- a/src/gpu/GrPathRendererChain.cpp
+++ b/src/gpu/GrPathRendererChain.cpp
@@ -15,10 +15,9 @@
SK_DEFINE_INST_COUNT(GrPathRendererChain)
-GrPathRendererChain::GrPathRendererChain(GrContext* context, UsageFlags flags)
+GrPathRendererChain::GrPathRendererChain(GrContext* context)
: fInit(false)
- , fOwner(context)
- , fFlags(flags) {
+ , fOwner(context) {
}
GrPathRendererChain::~GrPathRendererChain() {
@@ -34,14 +33,43 @@ GrPathRenderer* GrPathRendererChain::addPathRenderer(GrPathRenderer* pr) {
}
GrPathRenderer* GrPathRendererChain::getPathRenderer(const SkPath& path,
- GrPathFill fill,
+ const SkStrokeRec& stroke,
const GrDrawTarget* target,
- bool antiAlias) {
+ DrawType drawType,
+ StencilSupport* stencilSupport) {
if (!fInit) {
this->init();
}
+ bool antiAlias = (kColorAntiAlias_DrawType == drawType ||
+ kStencilAndColorAntiAlias_DrawType == drawType);
+
+ GR_STATIC_ASSERT(GrPathRenderer::kNoSupport_StencilSupport <
+ GrPathRenderer::kStencilOnly_StencilSupport);
+ GR_STATIC_ASSERT(GrPathRenderer::kStencilOnly_StencilSupport <
+ GrPathRenderer::kNoRestriction_StencilSupport);
+ GrPathRenderer::StencilSupport minStencilSupport;
+ if (kStencilOnly_DrawType == drawType) {
+ minStencilSupport = GrPathRenderer::kStencilOnly_StencilSupport;
+ } else if (kStencilAndColor_DrawType == drawType ||
+ kStencilAndColorAntiAlias_DrawType == drawType) {
+ minStencilSupport = GrPathRenderer::kNoRestriction_StencilSupport;
+ } else {
+ minStencilSupport = GrPathRenderer::kNoSupport_StencilSupport;
+ }
+
+
for (int i = 0; i < fChain.count(); ++i) {
- if (fChain[i]->canDrawPath(path, fill, target, antiAlias)) {
+ if (fChain[i]->canDrawPath(path, stroke, target, antiAlias)) {
+ if (GrPathRenderer::kNoSupport_StencilSupport != minStencilSupport) {
+ GrPathRenderer::StencilSupport support = fChain[i]->getStencilSupport(path,
+ stroke,
+ target);
+ if (support < minStencilSupport) {
+ continue;
+ } else if (NULL != stencilSupport) {
+ *stencilSupport = support;
+ }
+ }
return fChain[i];
}
}
@@ -53,7 +81,7 @@ void GrPathRendererChain::init() {
GrGpu* gpu = fOwner->getGpu();
bool twoSided = gpu->getCaps().twoSidedStencilSupport();
bool wrapOp = gpu->getCaps().stencilWrapOpsSupport();
- GrPathRenderer::AddPathRenderers(fOwner, fFlags, this);
+ GrPathRenderer::AddPathRenderers(fOwner, this);
this->addPathRenderer(SkNEW_ARGS(GrDefaultPathRenderer,
(twoSided, wrapOp)))->unref();
fInit = true;
diff --git a/src/gpu/GrPathRendererChain.h b/src/gpu/GrPathRendererChain.h
deleted file mode 100644
index e5ccabb95d..0000000000
--- a/src/gpu/GrPathRendererChain.h
+++ /dev/null
@@ -1,68 +0,0 @@
-
-/*
- * Copyright 2011 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-
-#ifndef GrPathRendererChain_DEFINED
-#define GrPathRendererChain_DEFINED
-
-#include "GrDrawTarget.h"
-#include "GrRefCnt.h"
-#include "SkTArray.h"
-
-class GrContext;
-
-class SkPath;
-class GrPathRenderer;
-
-/**
- * Keeps track of an ordered list of path renderers. When a path needs to be
- * drawn this list is scanned to find the most preferred renderer. To add your
- * path renderer to the list implement the GrPathRenderer::AddPathRenderers
- * function.
- */
-class GrPathRendererChain : public SkRefCnt {
-public:
- SK_DECLARE_INST_COUNT(GrPathRendererChain)
-
- enum UsageFlags {
- kNone_UsageFlag = 0,
- kNonAAOnly_UsageFlag = 1,
- };
-
- GrPathRendererChain(GrContext* context, UsageFlags flags);
-
- ~GrPathRendererChain();
-
- // takes a ref and unrefs in destructor
- GrPathRenderer* addPathRenderer(GrPathRenderer* pr);
-
- GrPathRenderer* getPathRenderer(const SkPath& path,
- GrPathFill fill,
- const GrDrawTarget* target,
- bool antiAlias);
-
-private:
-
- GrPathRendererChain();
-
- void init();
-
- enum {
- kPreAllocCount = 8,
- };
- bool fInit;
- GrContext* fOwner;
- UsageFlags fFlags;
- SkSTArray<kPreAllocCount, GrPathRenderer*, true> fChain;
-
- typedef SkRefCnt INHERITED;
-};
-
-GR_MAKE_BITFIELD_OPS(GrPathRendererChain::UsageFlags)
-
-#endif
diff --git a/src/gpu/GrPathUtils.cpp b/src/gpu/GrPathUtils.cpp
index e0ddea868f..4092959c29 100644
--- a/src/gpu/GrPathUtils.cpp
+++ b/src/gpu/GrPathUtils.cpp
@@ -11,40 +11,40 @@
#include "GrPoint.h"
#include "SkGeometry.h"
-GrScalar GrPathUtils::scaleToleranceToSrc(GrScalar devTol,
- const GrMatrix& viewM,
+SkScalar GrPathUtils::scaleToleranceToSrc(SkScalar devTol,
+ const SkMatrix& viewM,
const GrRect& pathBounds) {
// In order to tesselate the path we get a bound on how much the matrix can
// stretch when mapping to screen coordinates.
- GrScalar stretch = viewM.getMaxStretch();
- GrScalar srcTol = devTol;
+ SkScalar stretch = viewM.getMaxStretch();
+ SkScalar srcTol = devTol;
if (stretch < 0) {
// take worst case mapRadius amoung four corners.
// (less than perfect)
for (int i = 0; i < 4; ++i) {
- GrMatrix mat;
+ SkMatrix mat;
mat.setTranslate((i % 2) ? pathBounds.fLeft : pathBounds.fRight,
(i < 2) ? pathBounds.fTop : pathBounds.fBottom);
mat.postConcat(viewM);
stretch = SkMaxScalar(stretch, mat.mapRadius(SK_Scalar1));
}
}
- srcTol = GrScalarDiv(srcTol, stretch);
+ srcTol = SkScalarDiv(srcTol, stretch);
return srcTol;
}
static const int MAX_POINTS_PER_CURVE = 1 << 10;
-static const GrScalar gMinCurveTol = GrFloatToScalar(0.0001f);
+static const SkScalar gMinCurveTol = SkFloatToScalar(0.0001f);
uint32_t GrPathUtils::quadraticPointCount(const GrPoint points[],
- GrScalar tol) {
+ SkScalar tol) {
if (tol < gMinCurveTol) {
tol = gMinCurveTol;
}
GrAssert(tol > 0);
- GrScalar d = points[1].distanceToLineSegmentBetween(points[0], points[2]);
+ SkScalar d = points[1].distanceToLineSegmentBetween(points[0], points[2]);
if (d <= tol) {
return 1;
} else {
@@ -67,7 +67,7 @@ uint32_t GrPathUtils::quadraticPointCount(const GrPoint points[],
uint32_t GrPathUtils::generateQuadraticPoints(const GrPoint& p0,
const GrPoint& p1,
const GrPoint& p2,
- GrScalar tolSqd,
+ SkScalar tolSqd,
GrPoint** points,
uint32_t pointsLeft) {
if (pointsLeft < 2 ||
@@ -78,10 +78,10 @@ uint32_t GrPathUtils::generateQuadraticPoints(const GrPoint& p0,
}
GrPoint q[] = {
- { GrScalarAve(p0.fX, p1.fX), GrScalarAve(p0.fY, p1.fY) },
- { GrScalarAve(p1.fX, p2.fX), GrScalarAve(p1.fY, p2.fY) },
+ { SkScalarAve(p0.fX, p1.fX), SkScalarAve(p0.fY, p1.fY) },
+ { SkScalarAve(p1.fX, p2.fX), SkScalarAve(p1.fY, p2.fY) },
};
- GrPoint r = { GrScalarAve(q[0].fX, q[1].fX), GrScalarAve(q[0].fY, q[1].fY) };
+ GrPoint r = { SkScalarAve(q[0].fX, q[1].fX), SkScalarAve(q[0].fY, q[1].fY) };
pointsLeft >>= 1;
uint32_t a = generateQuadraticPoints(p0, q[0], r, tolSqd, points, pointsLeft);
@@ -90,13 +90,13 @@ uint32_t GrPathUtils::generateQuadraticPoints(const GrPoint& p0,
}
uint32_t GrPathUtils::cubicPointCount(const GrPoint points[],
- GrScalar tol) {
+ SkScalar tol) {
if (tol < gMinCurveTol) {
tol = gMinCurveTol;
}
GrAssert(tol > 0);
- GrScalar d = GrMax(
+ SkScalar d = GrMax(
points[1].distanceToLineSegmentBetweenSqd(points[0], points[3]),
points[2].distanceToLineSegmentBetweenSqd(points[0], points[3]));
d = SkScalarSqrt(d);
@@ -119,7 +119,7 @@ uint32_t GrPathUtils::generateCubicPoints(const GrPoint& p0,
const GrPoint& p1,
const GrPoint& p2,
const GrPoint& p3,
- GrScalar tolSqd,
+ SkScalar tolSqd,
GrPoint** points,
uint32_t pointsLeft) {
if (pointsLeft < 2 ||
@@ -130,15 +130,15 @@ uint32_t GrPathUtils::generateCubicPoints(const GrPoint& p0,
return 1;
}
GrPoint q[] = {
- { GrScalarAve(p0.fX, p1.fX), GrScalarAve(p0.fY, p1.fY) },
- { GrScalarAve(p1.fX, p2.fX), GrScalarAve(p1.fY, p2.fY) },
- { GrScalarAve(p2.fX, p3.fX), GrScalarAve(p2.fY, p3.fY) }
+ { SkScalarAve(p0.fX, p1.fX), SkScalarAve(p0.fY, p1.fY) },
+ { SkScalarAve(p1.fX, p2.fX), SkScalarAve(p1.fY, p2.fY) },
+ { SkScalarAve(p2.fX, p3.fX), SkScalarAve(p2.fY, p3.fY) }
};
GrPoint r[] = {
- { GrScalarAve(q[0].fX, q[1].fX), GrScalarAve(q[0].fY, q[1].fY) },
- { GrScalarAve(q[1].fX, q[2].fX), GrScalarAve(q[1].fY, q[2].fY) }
+ { SkScalarAve(q[0].fX, q[1].fX), SkScalarAve(q[0].fY, q[1].fY) },
+ { SkScalarAve(q[1].fX, q[2].fX), SkScalarAve(q[1].fY, q[2].fY) }
};
- GrPoint s = { GrScalarAve(r[0].fX, r[1].fX), GrScalarAve(r[0].fY, r[1].fY) };
+ GrPoint s = { SkScalarAve(r[0].fX, r[1].fX), SkScalarAve(r[0].fY, r[1].fY) };
pointsLeft >>= 1;
uint32_t a = generateCubicPoints(p0, q[0], r[0], s, tolSqd, points, pointsLeft);
uint32_t b = generateCubicPoints(s, r[1], q[2], p3, tolSqd, points, pointsLeft);
@@ -146,7 +146,7 @@ uint32_t GrPathUtils::generateCubicPoints(const GrPoint& p0,
}
int GrPathUtils::worstCasePointCount(const SkPath& path, int* subpaths,
- GrScalar tol) {
+ SkScalar tol) {
if (tol < gMinCurveTol) {
tol = gMinCurveTol;
}
@@ -199,16 +199,16 @@ void GrPathUtils::QuadUVMatrix::set(const GrPoint qPts[3]) {
// [0 0 1]
// [1 1 1]
// We invert the control pt matrix and post concat to both sides to get M.
- UVpts.setAll(0, GR_ScalarHalf, GR_Scalar1,
- 0, 0, GR_Scalar1,
- SkScalarToPersp(GR_Scalar1),
- SkScalarToPersp(GR_Scalar1),
- SkScalarToPersp(GR_Scalar1));
+ UVpts.setAll(0, SK_ScalarHalf, SK_Scalar1,
+ 0, 0, SK_Scalar1,
+ SkScalarToPersp(SK_Scalar1),
+ SkScalarToPersp(SK_Scalar1),
+ SkScalarToPersp(SK_Scalar1));
m.setAll(qPts[0].fX, qPts[1].fX, qPts[2].fX,
qPts[0].fY, qPts[1].fY, qPts[2].fY,
- SkScalarToPersp(GR_Scalar1),
- SkScalarToPersp(GR_Scalar1),
- SkScalarToPersp(GR_Scalar1));
+ SkScalarToPersp(SK_Scalar1),
+ SkScalarToPersp(SK_Scalar1),
+ SkScalarToPersp(SK_Scalar1));
if (!m.invert(&m)) {
// The quad is degenerate. Hopefully this is rare. Find the pts that are
// farthest apart to compute a line (unless it is really a pt).
@@ -251,9 +251,9 @@ void GrPathUtils::QuadUVMatrix::set(const GrPoint qPts[3]) {
m.postConcat(UVpts);
// The matrix should not have perspective.
- static const GrScalar gTOL = GrFloatToScalar(1.f / 100.f);
- GrAssert(GrScalarAbs(m.get(SkMatrix::kMPersp0)) < gTOL);
- GrAssert(GrScalarAbs(m.get(SkMatrix::kMPersp1)) < gTOL);
+ static const SkScalar gTOL = SkFloatToScalar(1.f / 100.f);
+ GrAssert(SkScalarAbs(m.get(SkMatrix::kMPersp0)) < gTOL);
+ GrAssert(SkScalarAbs(m.get(SkMatrix::kMPersp1)) < gTOL);
// It may not be normalized to have 1.0 in the bottom right
float m33 = m.get(SkMatrix::kMPersp2);
diff --git a/src/gpu/GrPathUtils.h b/src/gpu/GrPathUtils.h
index 31b639864e..2d6045e00c 100644
--- a/src/gpu/GrPathUtils.h
+++ b/src/gpu/GrPathUtils.h
@@ -10,44 +10,46 @@
#ifndef GrPathUtils_DEFINED
#define GrPathUtils_DEFINED
-#include "GrMatrix.h"
+#include "GrRect.h"
#include "SkPath.h"
#include "SkTArray.h"
+class SkMatrix;
+
/**
* Utilities for evaluating paths.
*/
namespace GrPathUtils {
- GrScalar scaleToleranceToSrc(GrScalar devTol,
- const GrMatrix& viewM,
+ SkScalar scaleToleranceToSrc(SkScalar devTol,
+ const SkMatrix& viewM,
const GrRect& pathBounds);
/// Since we divide by tol if we're computing exact worst-case bounds,
/// very small tolerances will be increased to gMinCurveTol.
int worstCasePointCount(const SkPath&,
int* subpaths,
- GrScalar tol);
+ SkScalar tol);
/// Since we divide by tol if we're computing exact worst-case bounds,
/// very small tolerances will be increased to gMinCurveTol.
- uint32_t quadraticPointCount(const GrPoint points[], GrScalar tol);
+ uint32_t quadraticPointCount(const GrPoint points[], SkScalar tol);
uint32_t generateQuadraticPoints(const GrPoint& p0,
const GrPoint& p1,
const GrPoint& p2,
- GrScalar tolSqd,
+ SkScalar tolSqd,
GrPoint** points,
uint32_t pointsLeft);
/// Since we divide by tol if we're computing exact worst-case bounds,
/// very small tolerances will be increased to gMinCurveTol.
- uint32_t cubicPointCount(const GrPoint points[], GrScalar tol);
+ uint32_t cubicPointCount(const GrPoint points[], SkScalar tol);
uint32_t generateCubicPoints(const GrPoint& p0,
const GrPoint& p1,
const GrPoint& p2,
const GrPoint& p3,
- GrScalar tolSqd,
+ SkScalar tolSqd,
GrPoint** points,
uint32_t pointsLeft);
diff --git a/src/gpu/GrRandom.h b/src/gpu/GrRandom.h
deleted file mode 100644
index c98a8fbd94..0000000000
--- a/src/gpu/GrRandom.h
+++ /dev/null
@@ -1,55 +0,0 @@
-
-/*
- * Copyright 2010 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-
-
-#ifndef GrRandom_DEFINED
-#define GrRandom_DEFINED
-
-class GrRandom {
-public:
- GrRandom() : fSeed(0) {}
- GrRandom(uint32_t seed) : fSeed(seed) {}
-
- uint32_t seed() const { return fSeed; }
-
- uint32_t nextU() {
- fSeed = fSeed * kMUL + kADD;
- return fSeed;
- }
-
- int32_t nextS() { return (int32_t)this->nextU(); }
-
- /**
- * Returns value [0...1) as a float
- */
- float nextF() {
- // const is 1 / (2^32 - 1)
- return (float)(this->nextU() * 2.32830644e-10);
- }
-
- /**
- * Returns value [min...max) as a float
- */
- float nextF(float min, float max) {
- return min + this->nextF() * (max - min);
- }
-
-private:
- /*
- * These constants taken from "Numerical Recipes in C", reprinted 1999
- */
- enum {
- kMUL = 1664525,
- kADD = 1013904223
- };
- uint32_t fSeed;
-};
-
-#endif
-
diff --git a/src/gpu/GrRectanizer.cpp b/src/gpu/GrRectanizer.cpp
index 87ea92e27a..652cc29742 100644
--- a/src/gpu/GrRectanizer.cpp
+++ b/src/gpu/GrRectanizer.cpp
@@ -48,7 +48,7 @@ public:
static int HeightToRowIndex(int height) {
GrAssert(height >= MIN_HEIGHT_POW2);
- return 32 - Gr_clz(height - 1);
+ return 32 - SkCLZ(height - 1);
}
int fNextStripY;
diff --git a/src/gpu/GrRedBlackTree.h b/src/gpu/GrRedBlackTree.h
index 8187d8160d..5038bb07bd 100644
--- a/src/gpu/GrRedBlackTree.h
+++ b/src/gpu/GrRedBlackTree.h
@@ -943,14 +943,14 @@ bool GrRedBlackTree<T,C>::validateChildRelations(const Node* n,
}
#endif
-#include "GrRandom.h"
+#include "SkRandom.h"
template <typename T, typename C>
void GrRedBlackTree<T,C>::UnitTest() {
GrRedBlackTree<int> tree;
typedef GrRedBlackTree<int>::Iter iter;
- GrRandom r;
+ SkRandom r;
int count[100] = {0};
// add 10K ints
diff --git a/src/gpu/GrReducedClip.cpp b/src/gpu/GrReducedClip.cpp
new file mode 100644
index 0000000000..da42e8cff8
--- /dev/null
+++ b/src/gpu/GrReducedClip.cpp
@@ -0,0 +1,415 @@
+
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrReducedClip.h"
+
+typedef SkClipStack::Element Element;
+////////////////////////////////////////////////////////////////////////////////
+
+namespace GrReducedClip {
+
+// helper function
+void reduced_stack_walker(const SkClipStack& stack,
+ const SkRect& queryBounds,
+ ElementList* result,
+ InitialState* initialState,
+ bool* requiresAA);
+
+/*
+There are plenty of optimizations that could be added here. Maybe flips could be folded into
+earlier operations. Or would inserting flips and reversing earlier ops ever be a win? Perhaps
+for the case where the bounds are kInsideOut_BoundsType. We could restrict earlier operations
+based on later intersect operations, and perhaps remove intersect-rects. We could optionally
+take a rect in case the caller knows a bound on what is to be drawn through this clip.
+*/
+void ReduceClipStack(const SkClipStack& stack,
+ const SkIRect& queryBounds,
+ ElementList* result,
+ InitialState* initialState,
+ SkIRect* tighterBounds,
+ bool* requiresAA) {
+ result->reset();
+
+ if (stack.isWideOpen()) {
+ *initialState = kAllIn_InitialState;
+ return;
+ }
+
+
+ // We initially look at whether the bounds alone is sufficient. We also use the stack bounds to
+ // attempt to compute the tighterBounds.
+
+ SkClipStack::BoundsType stackBoundsType;
+ SkRect stackBounds;
+ bool iior;
+ stack.getBounds(&stackBounds, &stackBoundsType, &iior);
+
+ const SkIRect* bounds = &queryBounds;
+
+ SkRect scalarQueryBounds = SkRect::MakeFromIRect(queryBounds);
+
+ if (iior) {
+ SkASSERT(SkClipStack::kNormal_BoundsType == stackBoundsType);
+ SkRect isectRect;
+ if (stackBounds.contains(scalarQueryBounds)) {
+ *initialState = kAllIn_InitialState;
+ if (NULL != tighterBounds) {
+ *tighterBounds = queryBounds;
+ }
+ if (NULL != requiresAA) {
+ *requiresAA = false;
+ }
+ } else if (isectRect.intersect(stackBounds, scalarQueryBounds)) {
+ if (NULL != tighterBounds) {
+ isectRect.roundOut(tighterBounds);
+ SkRect scalarTighterBounds = SkRect::MakeFromIRect(*tighterBounds);
+ if (scalarTighterBounds == isectRect) {
+ // the round-out didn't add any area outside the clip rect.
+ *requiresAA = false;
+ *initialState = kAllIn_InitialState;
+ return;
+ }
+ *initialState = kAllOut_InitialState;
+ // iior should only be true if aa/non-aa status matches among all elements.
+ SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
+ bool doAA = iter.prev()->isAA();
+ SkNEW_INSERT_AT_LLIST_HEAD(result, Element, (isectRect, SkRegion::kReplace_Op, doAA));
+ if (NULL != requiresAA) {
+ *requiresAA = doAA;
+ }
+ }
+ } else {
+ *initialState = kAllOut_InitialState;
+ if (NULL != requiresAA) {
+ *requiresAA = false;
+ }
+ }
+ return;
+ } else {
+ if (SkClipStack::kNormal_BoundsType == stackBoundsType) {
+ if (!SkRect::Intersects(stackBounds, scalarQueryBounds)) {
+ *initialState = kAllOut_InitialState;
+ if (NULL != requiresAA) {
+ *requiresAA = false;
+ }
+ return;
+ }
+ if (NULL != tighterBounds) {
+ SkIRect stackIBounds;
+ stackBounds.roundOut(&stackIBounds);
+ tighterBounds->intersect(queryBounds, stackIBounds);
+ bounds = tighterBounds;
+ }
+ } else {
+ if (stackBounds.contains(scalarQueryBounds)) {
+ *initialState = kAllOut_InitialState;
+ if (NULL != requiresAA) {
+ *requiresAA = false;
+ }
+ return;
+ }
+ if (NULL != tighterBounds) {
+ *tighterBounds = queryBounds;
+ }
+ }
+ }
+
+ SkRect scalarBounds = SkRect::MakeFromIRect(*bounds);
+
+ // Now that we have determined the bounds to use and filtered out the trivial cases, call the
+ // helper that actually walks the stack.
+ reduced_stack_walker(stack, scalarBounds, result, initialState, requiresAA);
+}
+
+void reduced_stack_walker(const SkClipStack& stack,
+ const SkRect& queryBounds,
+ ElementList* result,
+ InitialState* initialState,
+ bool* requiresAA) {
+
+ // walk backwards until we get to:
+ // a) the beginning
+ // b) an operation that is known to make the bounds all inside/outside
+ // c) a replace operation
+
+ static const InitialState kUnknown_InitialState = static_cast<InitialState>(-1);
+ *initialState = kUnknown_InitialState;
+
+ // During our backwards walk, track whether we've seen ops that either grow or shrink the clip.
+ // TODO: track these per saved clip so that we can consider them on the forward pass.
+ bool embiggens = false;
+ bool emsmallens = false;
+
+ SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
+ int numAAElements = 0;
+ while ((kUnknown_InitialState == *initialState)) {
+ const Element* element = iter.prev();
+ if (NULL == element) {
+ *initialState = kAllIn_InitialState;
+ break;
+ }
+ if (SkClipStack::kEmptyGenID == element->getGenID()) {
+ *initialState = kAllOut_InitialState;
+ break;
+ }
+ if (SkClipStack::kWideOpenGenID == element->getGenID()) {
+ *initialState = kAllIn_InitialState;
+ break;
+ }
+
+ bool skippable = false;
+ bool isFlip = false; // does this op just flip the in/out state of every point in the bounds
+
+ switch (element->getOp()) {
+ case SkRegion::kDifference_Op:
+ // check if the shape subtracted either contains the entire bounds (and makes
+ // the clip empty) or is outside the bounds and therefore can be skipped.
+ if (element->isInverseFilled()) {
+ if (element->contains(queryBounds)) {
+ skippable = true;
+ } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
+ *initialState = kAllOut_InitialState;
+ skippable = true;
+ }
+ } else {
+ if (element->contains(queryBounds)) {
+ *initialState = kAllOut_InitialState;
+ skippable = true;
+ } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
+ skippable = true;
+ }
+ }
+ if (!skippable) {
+ emsmallens = true;
+ }
+ break;
+ case SkRegion::kIntersect_Op:
+ // check if the shape intersected contains the entire bounds and therefore can
+ // be skipped or it is outside the entire bounds and therefore makes the clip
+ // empty.
+ if (element->isInverseFilled()) {
+ if (element->contains(queryBounds)) {
+ *initialState = kAllOut_InitialState;
+ skippable = true;
+ } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
+ skippable = true;
+ }
+ } else {
+ if (element->contains(queryBounds)) {
+ skippable = true;
+ } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
+ *initialState = kAllOut_InitialState;
+ skippable = true;
+ }
+ }
+ if (!skippable) {
+ emsmallens = true;
+ }
+ break;
+ case SkRegion::kUnion_Op:
+ // If the union-ed shape contains the entire bounds then after this element
+ // the bounds is entirely inside the clip. If the union-ed shape is outside the
+ // bounds then this op can be skipped.
+ if (element->isInverseFilled()) {
+ if (element->contains(queryBounds)) {
+ skippable = true;
+ } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
+ *initialState = kAllIn_InitialState;
+ skippable = true;
+ }
+ } else {
+ if (element->contains(queryBounds)) {
+ *initialState = kAllIn_InitialState;
+ skippable = true;
+ } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
+ skippable = true;
+ }
+ }
+ if (!skippable) {
+ embiggens = true;
+ }
+ break;
+ case SkRegion::kXOR_Op:
+ // If the bounds is entirely inside the shape being xor-ed then the effect is
+ // to flip the inside/outside state of every point in the bounds. We may be
+ // able to take advantage of this in the forward pass. If the xor-ed shape
+ // doesn't intersect the bounds then it can be skipped.
+ if (element->isInverseFilled()) {
+ if (element->contains(queryBounds)) {
+ skippable = true;
+ } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
+ isFlip = true;
+ }
+ } else {
+ if (element->contains(queryBounds)) {
+ isFlip = true;
+ } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
+ skippable = true;
+ }
+ }
+ if (!skippable) {
+ emsmallens = embiggens = true;
+ }
+ break;
+ case SkRegion::kReverseDifference_Op:
+ // When the bounds is entirely within the rev-diff shape then this behaves like xor
+ // and reverses every point inside the bounds. If the shape is completely outside
+ // the bounds then we know after this element is applied that the bounds will be
+ // all outside the current clip.B
+ if (element->isInverseFilled()) {
+ if (element->contains(queryBounds)) {
+ *initialState = kAllOut_InitialState;
+ skippable = true;
+ } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
+ isFlip = true;
+ }
+ } else {
+ if (element->contains(queryBounds)) {
+ isFlip = true;
+ } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
+ *initialState = kAllOut_InitialState;
+ skippable = true;
+ }
+ }
+ if (!skippable) {
+ emsmallens = embiggens = true;
+ }
+ break;
+ case SkRegion::kReplace_Op:
+ // Replace will always terminate our walk. We will either begin the forward walk
+ // at the replace op or detect here than the shape is either completely inside
+ // or completely outside the bounds. In this latter case it can be skipped by
+ // setting the correct value for initialState.
+ if (element->isInverseFilled()) {
+ if (element->contains(queryBounds)) {
+ *initialState = kAllOut_InitialState;
+ skippable = true;
+ } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
+ *initialState = kAllIn_InitialState;
+ skippable = true;
+ }
+ } else {
+ if (element->contains(queryBounds)) {
+ *initialState = kAllIn_InitialState;
+ skippable = true;
+ } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
+ *initialState = kAllOut_InitialState;
+ skippable = true;
+ }
+ }
+ if (!skippable) {
+ *initialState = kAllOut_InitialState;
+ embiggens = emsmallens = true;
+ }
+ break;
+ default:
+ SkDEBUGFAIL("Unexpected op.");
+ break;
+ }
+ if (!skippable) {
+ // if it is a flip, change it to a bounds-filling rect
+ if (isFlip) {
+ SkASSERT(SkRegion::kXOR_Op == element->getOp() ||
+ SkRegion::kReverseDifference_Op == element->getOp());
+ SkNEW_INSERT_AT_LLIST_HEAD(result,
+ Element,
+ (queryBounds, SkRegion::kReverseDifference_Op, false));
+ } else {
+ Element* newElement = result->addToHead(*element);
+ if (newElement->isAA()) {
+ ++numAAElements;
+ }
+ // Intersecting an inverse shape is the same as differencing the non-inverse shape.
+ // Replacing with an inverse shape is the same as setting initialState=kAllIn and
+ // differencing the non-inverse shape.
+ bool isReplace = SkRegion::kReplace_Op == newElement->getOp();
+ if (newElement->isInverseFilled() &&
+ (SkRegion::kIntersect_Op == newElement->getOp() || isReplace)) {
+ newElement->invertShapeFillType();
+ newElement->setOp(SkRegion::kDifference_Op);
+ if (isReplace) {
+ SkASSERT(kAllOut_InitialState == *initialState);
+ *initialState = kAllIn_InitialState;
+ }
+ }
+ }
+ }
+ }
+
+ if ((kAllOut_InitialState == *initialState && !embiggens) ||
+ (kAllIn_InitialState == *initialState && !emsmallens)) {
+ result->reset();
+ } else {
+ Element* element = result->headIter().get();
+ while (NULL != element) {
+ bool skippable = false;
+ switch (element->getOp()) {
+ case SkRegion::kDifference_Op:
+ // subtracting from the empty set yields the empty set.
+ skippable = kAllOut_InitialState == *initialState;
+ break;
+ case SkRegion::kIntersect_Op:
+ // intersecting with the empty set yields the empty set
+ skippable = kAllOut_InitialState == *initialState;
+ break;
+ case SkRegion::kUnion_Op:
+ if (kAllIn_InitialState == *initialState) {
+ // unioning the infinite plane with anything is a no-op.
+ skippable = true;
+ } else {
+ // unioning the empty set with a shape is the shape.
+ element->setOp(SkRegion::kReplace_Op);
+ }
+ break;
+ case SkRegion::kXOR_Op:
+ if (kAllOut_InitialState == *initialState) {
+ // xor could be changed to diff in the kAllIn case, not sure it's a win.
+ element->setOp(SkRegion::kReplace_Op);
+ }
+ break;
+ case SkRegion::kReverseDifference_Op:
+ if (kAllIn_InitialState == *initialState) {
+ // subtracting the whole plane will yield the empty set.
+ skippable = true;
+ *initialState = kAllOut_InitialState;
+ } else {
+ // this picks up flips inserted in the backwards pass.
+ skippable = element->isInverseFilled() ?
+ !SkRect::Intersects(element->getBounds(), queryBounds) :
+ element->contains(queryBounds);
+ if (skippable) {
+ *initialState = kAllIn_InitialState;
+ } else {
+ element->setOp(SkRegion::kReplace_Op);
+ }
+ }
+ break;
+ case SkRegion::kReplace_Op:
+ skippable = false; // we would have skipped it in the backwards walk if we
+ // could've.
+ break;
+ default:
+ SkDEBUGFAIL("Unexpected op.");
+ break;
+ }
+ if (!skippable) {
+ break;
+ } else {
+ if (element->isAA()) {
+ --numAAElements;
+ }
+ result->popHead();
+ element = result->headIter().get();
+ }
+ }
+ }
+ if (NULL != requiresAA) {
+ *requiresAA = numAAElements > 0;
+ }
+}
+} // namespace GrReducedClip
diff --git a/src/gpu/GrReducedClip.h b/src/gpu/GrReducedClip.h
new file mode 100644
index 0000000000..abfc244f20
--- /dev/null
+++ b/src/gpu/GrReducedClip.h
@@ -0,0 +1,40 @@
+
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkClipStack.h"
+#include "SkTLList.h"
+
+namespace GrReducedClip {
+
+typedef SkTLList<SkClipStack::Element> ElementList;
+
+enum InitialState {
+ kAllIn_InitialState,
+ kAllOut_InitialState,
+};
+
+/**
+ * This function takes a clip stack and a query rectangle and it produces a reduced set of
+ * SkClipStack::Elements that are equivalent to applying the full stack to the rectangle. The
+ * initial state of the query rectangle before the first clip element is applied is returned via
+ * initialState. Optionally, the caller can request a tighter bounds on the clip be returned via
+ * tighterBounds. If not NULL, tighterBounds will always be contained by queryBounds after return.
+ * If tighterBounds is specified then it is assumed that the caller will implicitly clip against it.
+ * If the caller specifies non-NULL for requiresAA then it will indicate whether anti-aliasing is
+ * required to process any of the elements in the result.
+ *
+ * This may become a member function of SkClipStack when its interface is determined to be stable.
+ */
+void ReduceClipStack(const SkClipStack& stack,
+ const SkIRect& queryBounds,
+ ElementList* result,
+ InitialState* initialState,
+ SkIRect* tighterBounds = NULL,
+ bool* requiresAA = NULL);
+
+} // namespace GrReducedClip
diff --git a/src/gpu/GrRenderTarget.cpp b/src/gpu/GrRenderTarget.cpp
index 0d36fd0319..5630d58297 100644
--- a/src/gpu/GrRenderTarget.cpp
+++ b/src/gpu/GrRenderTarget.cpp
@@ -95,6 +95,10 @@ void GrRenderTarget::overrideResolveRect(const GrIRect rect) {
}
void GrRenderTarget::setStencilBuffer(GrStencilBuffer* stencilBuffer) {
+ if (stencilBuffer == fStencilBuffer) {
+ return;
+ }
+
if (NULL != fStencilBuffer) {
fStencilBuffer->unref();
diff --git a/src/gpu/GrResourceCache.cpp b/src/gpu/GrResourceCache.cpp
index 89fce55f70..a1f1d794e5 100644
--- a/src/gpu/GrResourceCache.cpp
+++ b/src/gpu/GrResourceCache.cpp
@@ -94,7 +94,7 @@ GrResourceCache::~GrResourceCache() {
fCache.remove(entry->fKey, entry);
// remove from our llist
- this->internalDetach(entry, false);
+ this->internalDetach(entry);
delete entry;
}
@@ -121,11 +121,11 @@ void GrResourceCache::setLimits(int maxResources, size_t maxResourceBytes) {
}
void GrResourceCache::internalDetach(GrResourceEntry* entry,
- bool clientDetach) {
+ BudgetBehaviors behavior) {
fList.remove(entry);
// update our stats
- if (clientDetach) {
+ if (kIgnore_BudgetBehavior == behavior) {
fClientDetachedCount += 1;
fClientDetachedBytes += entry->resource()->sizeInBytes();
@@ -139,20 +139,24 @@ void GrResourceCache::internalDetach(GrResourceEntry* entry,
#endif
} else {
+ GrAssert(kAccountFor_BudgetBehavior == behavior);
+
fEntryCount -= 1;
fEntryBytes -= entry->resource()->sizeInBytes();
}
}
void GrResourceCache::attachToHead(GrResourceEntry* entry,
- bool clientReattach) {
+ BudgetBehaviors behavior) {
fList.addToHead(entry);
// update our stats
- if (clientReattach) {
+ if (kIgnore_BudgetBehavior == behavior) {
fClientDetachedCount -= 1;
fClientDetachedBytes -= entry->resource()->sizeInBytes();
} else {
+ GrAssert(kAccountFor_BudgetBehavior == behavior);
+
fEntryCount += 1;
fEntryBytes += entry->resource()->sizeInBytes();
@@ -167,16 +171,40 @@ void GrResourceCache::attachToHead(GrResourceEntry* entry,
}
}
-GrResource* GrResourceCache::find(const GrResourceKey& key) {
+// This functor just searches for an entry with only a single ref (from
+// the texture cache itself). Presumably in this situation no one else
+// is relying on the texture.
+class GrTFindUnreffedFunctor {
+public:
+ bool operator()(const GrResourceEntry* entry) const {
+ return 1 == entry->resource()->getRefCnt();
+ }
+};
+
+GrResource* GrResourceCache::find(const GrResourceKey& key, uint32_t ownershipFlags) {
GrAutoResourceCacheValidate atcv(this);
- GrResourceEntry* entry = fCache.find(key);
+ GrResourceEntry* entry = NULL;
+
+ if (ownershipFlags & kNoOtherOwners_OwnershipFlag) {
+ GrTFindUnreffedFunctor functor;
+
+ entry = fCache.find<GrTFindUnreffedFunctor>(key, functor);
+ } else {
+ entry = fCache.find(key);
+ }
+
if (NULL == entry) {
return NULL;
}
- this->internalDetach(entry, false);
- this->attachToHead(entry, false);
+ if (ownershipFlags & kHide_OwnershipFlag) {
+ this->makeExclusive(entry);
+ } else {
+ // Make this resource MRU
+ this->internalDetach(entry);
+ this->attachToHead(entry);
+ }
return entry->fResource;
}
@@ -185,7 +213,9 @@ bool GrResourceCache::hasKey(const GrResourceKey& key) const {
return NULL != fCache.find(key);
}
-void GrResourceCache::create(const GrResourceKey& key, GrResource* resource) {
+void GrResourceCache::addResource(const GrResourceKey& key,
+ GrResource* resource,
+ uint32_t ownershipFlags) {
GrAssert(NULL == resource->getCacheEntry());
// we don't expect to create new resources during a purge. In theory
// this could cause purgeAsNeeded() into an infinite loop (e.g.
@@ -197,19 +227,26 @@ void GrResourceCache::create(const GrResourceKey& key, GrResource* resource) {
GrResourceEntry* entry = SkNEW_ARGS(GrResourceEntry, (key, resource));
resource->setCacheEntry(entry);
- this->attachToHead(entry, false);
+ this->attachToHead(entry);
fCache.insert(key, entry);
#if GR_DUMP_TEXTURE_UPLOAD
GrPrintf("--- add resource to cache %p, count=%d bytes= %d %d\n",
entry, fEntryCount, resource->sizeInBytes(), fEntryBytes);
#endif
+
+ if (ownershipFlags & kHide_OwnershipFlag) {
+ this->makeExclusive(entry);
+ }
+
}
void GrResourceCache::makeExclusive(GrResourceEntry* entry) {
GrAutoResourceCacheValidate atcv(this);
- this->internalDetach(entry, true);
+ // When scratch textures are detached (to hide them from future finds) they
+ // still count against the resource budget
+ this->internalDetach(entry, kIgnore_BudgetBehavior);
fCache.remove(entry->key(), entry);
#if GR_DEBUG
@@ -238,7 +275,10 @@ void GrResourceCache::makeNonExclusive(GrResourceEntry* entry) {
#endif
if (entry->resource()->isValid()) {
- attachToHead(entry, true);
+ // Since scratch textures still count against the cache budget even
+ // when they have been removed from the cache, re-adding them doesn't
+ // alter the budget information.
+ attachToHead(entry, kIgnore_BudgetBehavior);
fCache.insert(entry->key(), entry);
} else {
this->removeInvalidResource(entry);
@@ -290,7 +330,7 @@ void GrResourceCache::purgeAsNeeded() {
fCache.remove(entry->key(), entry);
// remove from our llist
- this->internalDetach(entry, false);
+ this->internalDetach(entry);
#if GR_DUMP_TEXTURE_UPLOAD
GrPrintf("--- ~resource from cache %p [%d %d]\n",
diff --git a/src/gpu/GrResourceCache.h b/src/gpu/GrResourceCache.h
index 7897b7ae47..e1207a204b 100644
--- a/src/gpu/GrResourceCache.h
+++ b/src/gpu/GrResourceCache.h
@@ -14,7 +14,7 @@
#include "GrConfig.h"
#include "GrTypes.h"
#include "GrTHashCache.h"
-#include "SkTDLinkedList.h"
+#include "SkTInternalLList.h"
class GrResource;
@@ -119,26 +119,6 @@ private:
};
-class GrCacheKey {
-public:
- GrCacheKey(const GrTextureDesc& desc, const GrResourceKey& key)
- : fDesc(desc)
- , fKey(key) {
- }
-
- void set(const GrTextureDesc& desc, const GrResourceKey& key) {
- fDesc = desc;
- fKey = key;
- }
-
- const GrTextureDesc& desc() const { return fDesc; }
- const GrResourceKey& key() const { return fKey; }
-
-protected:
- GrTextureDesc fDesc;
- GrResourceKey fKey;
-};
-
///////////////////////////////////////////////////////////////////////////////
class GrResourceEntry {
@@ -159,8 +139,8 @@ private:
GrResourceKey fKey;
GrResource* fResource;
- // we're a dlinklist
- SK_DEFINE_DLINKEDLIST_INTERFACE(GrResourceEntry);
+ // we're a linked list
+ SK_DECLARE_INTERNAL_LLIST_INTERFACE(GrResourceEntry);
friend class GrResourceCache;
friend class GrDLinkedList;
@@ -220,20 +200,41 @@ public:
*/
size_t getCachedResourceBytes() const { return fEntryBytes; }
+ // For a found or added resource to be completely exclusive to the caller
+ // both the kNoOtherOwners and kHide flags need to be specified
+ enum OwnershipFlags {
+ kNoOtherOwners_OwnershipFlag = 0x1, // found/added resource has no other owners
+ kHide_OwnershipFlag = 0x2 // found/added resource is hidden from future 'find's
+ };
+
/**
* Search for an entry with the same Key. If found, return it.
* If not found, return null.
+ * If ownershipFlags includes kNoOtherOwners and a resource is returned
+ * then that resource has no other refs to it.
+ * If ownershipFlags includes kHide and a resource is returned then that
+ * resource will not be returned from future 'find' calls until it is
+ * 'freed' (and recycled) or makeNonExclusive is called.
+ * For a resource to be completely exclusive to a caller both kNoOtherOwners
+ * and kHide must be specified.
*/
- GrResource* find(const GrResourceKey& key);
+ GrResource* find(const GrResourceKey& key,
+ uint32_t ownershipFlags = 0);
/**
- * Create a new cache entry, based on the provided key and resource, and
- * return it.
+ * Add the new resource to the cache (by creating a new cache entry based
+ * on the provided key and resource).
*
* Ownership of the resource is transferred to the resource cache,
* which will unref() it when it is purged or deleted.
+ *
+ * If ownershipFlags includes kHide, subsequent calls to 'find' will not
+ * return 'resource' until it is 'freed' (and recycled) or makeNonExclusive
+ * is called.
*/
- void create(const GrResourceKey&, GrResource*);
+ void addResource(const GrResourceKey& key,
+ GrResource* resource,
+ uint32_t ownershipFlags = 0);
/**
* Determines if the cache contains an entry matching a key. If a matching
@@ -278,16 +279,21 @@ public:
#endif
private:
- void internalDetach(GrResourceEntry*, bool);
- void attachToHead(GrResourceEntry*, bool);
+ enum BudgetBehaviors {
+ kAccountFor_BudgetBehavior,
+ kIgnore_BudgetBehavior
+ };
+
+ void internalDetach(GrResourceEntry*, BudgetBehaviors behavior = kAccountFor_BudgetBehavior);
+ void attachToHead(GrResourceEntry*, BudgetBehaviors behavior = kAccountFor_BudgetBehavior);
void removeInvalidResource(GrResourceEntry* entry);
class Key;
GrTHashTable<GrResourceEntry, Key, 8> fCache;
- // manage the dlink list
- typedef SkTDLinkedList<GrResourceEntry> EntryList;
+ // We're an internal doubly linked list
+ typedef SkTInternalLList<GrResourceEntry> EntryList;
EntryList fList;
#if GR_DEBUG
@@ -316,7 +322,7 @@ private:
bool fPurging;
#if GR_DEBUG
- static size_t countBytes(const SkTDLinkedList<GrResourceEntry>& list);
+ static size_t countBytes(const SkTInternalLList<GrResourceEntry>& list);
#endif
};
diff --git a/src/gpu/GrSWMaskHelper.cpp b/src/gpu/GrSWMaskHelper.cpp
index ec0dbf188f..32a945b8b1 100644
--- a/src/gpu/GrSWMaskHelper.cpp
+++ b/src/gpu/GrSWMaskHelper.cpp
@@ -9,6 +9,8 @@
#include "GrDrawState.h"
#include "GrGpu.h"
+#include "SkStrokeRec.h"
+
// TODO: try to remove this #include
#include "GrContext.h"
@@ -30,23 +32,6 @@ SkXfermode::Mode op_to_mode(SkRegion::Op op) {
return modeMap[op];
}
-////////////////////////////////////////////////////////////////////////////////
-SkPath::FillType gr_fill_to_sk_fill(GrPathFill fill) {
- switch (fill) {
- case kWinding_GrPathFill:
- return SkPath::kWinding_FillType;
- case kEvenOdd_GrPathFill:
- return SkPath::kEvenOdd_FillType;
- case kInverseWinding_GrPathFill:
- return SkPath::kInverseWinding_FillType;
- case kInverseEvenOdd_GrPathFill:
- return SkPath::kInverseEvenOdd_FillType;
- default:
- GrCrash("Unexpected fill.");
- return SkPath::kWinding_FillType;
- }
-}
-
}
/**
@@ -70,37 +55,37 @@ void GrSWMaskHelper::draw(const GrRect& rect, SkRegion::Op op,
/**
* Draw a single path element of the clip stack into the accumulation bitmap
*/
-void GrSWMaskHelper::draw(const SkPath& path, SkRegion::Op op,
- GrPathFill fill, bool antiAlias, uint8_t alpha) {
+void GrSWMaskHelper::draw(const SkPath& path, const SkStrokeRec& stroke, SkRegion::Op op,
+ bool antiAlias, uint8_t alpha) {
SkPaint paint;
- SkPath tmpPath;
- const SkPath* pathToDraw = &path;
- if (kHairLine_GrPathFill == fill) {
+ if (stroke.isHairlineStyle()) {
paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeWidth(SK_Scalar1);
} else {
- paint.setStyle(SkPaint::kFill_Style);
- SkPath::FillType skfill = gr_fill_to_sk_fill(fill);
- if (skfill != pathToDraw->getFillType()) {
- tmpPath = *pathToDraw;
- tmpPath.setFillType(skfill);
- pathToDraw = &tmpPath;
+ if (stroke.isFillStyle()) {
+ paint.setStyle(SkPaint::kFill_Style);
+ } else {
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setStrokeJoin(stroke.getJoin());
+ paint.setStrokeCap(stroke.getCap());
+ paint.setStrokeWidth(stroke.getWidth());
}
}
+
SkXfermode* mode = SkXfermode::Create(op_to_mode(op));
paint.setXfermode(mode);
paint.setAntiAlias(antiAlias);
paint.setColor(SkColorSetARGB(alpha, alpha, alpha, alpha));
- fDraw.drawPath(*pathToDraw, paint);
+ fDraw.drawPath(path, paint);
SkSafeUnref(mode);
}
bool GrSWMaskHelper::init(const GrIRect& resultBounds,
- const GrMatrix* matrix) {
+ const SkMatrix* matrix) {
if (NULL != matrix) {
fMatrix = *matrix;
} else {
@@ -159,7 +144,7 @@ void GrSWMaskHelper::toTexture(GrTexture *texture, uint8_t alpha) {
GrDrawState::AutoRenderTargetRestore artr(fContext->getGpu()->drawState(),
texture->asRenderTarget());
- fContext->getGpu()->clear(NULL, SkColorSetARGB(alpha, alpha, alpha, alpha));
+ fContext->getGpu()->clear(NULL, GrColorPackRGBA(alpha, alpha, alpha, alpha));
texture->writePixels(0, 0, fBM.width(), fBM.height(),
kAlpha_8_GrPixelConfig,
@@ -174,10 +159,10 @@ void GrSWMaskHelper::toTexture(GrTexture *texture, uint8_t alpha) {
*/
GrTexture* GrSWMaskHelper::DrawPathMaskToTexture(GrContext* context,
const SkPath& path,
+ const SkStrokeRec& stroke,
const GrIRect& resultBounds,
- GrPathFill fill,
bool antiAlias,
- GrMatrix* matrix) {
+ SkMatrix* matrix) {
GrAutoScratchTexture ast;
GrSWMaskHelper helper(context);
@@ -186,7 +171,7 @@ GrTexture* GrSWMaskHelper::DrawPathMaskToTexture(GrContext* context,
return NULL;
}
- helper.draw(path, SkRegion::kReplace_Op, fill, antiAlias, 0xFF);
+ helper.draw(path, stroke, SkRegion::kReplace_Op, antiAlias, 0xFF);
if (!helper.getTexture(&ast)) {
return NULL;
@@ -212,10 +197,10 @@ void GrSWMaskHelper::DrawToTargetWithPathMask(GrTexture* texture,
kPathMaskStage = GrPaint::kTotalStages,
};
GrAssert(!drawState->isStageEnabled(kPathMaskStage));
- drawState->sampler(kPathMaskStage)->reset();
+ drawState->stage(kPathMaskStage)->reset();
drawState->createTextureEffect(kPathMaskStage, texture);
- GrScalar w = GrIntToScalar(rect.width());
- GrScalar h = GrIntToScalar(rect.height());
+ SkScalar w = SkIntToScalar(rect.width());
+ SkScalar h = SkIntToScalar(rect.height());
GrRect maskRect = GrRect::MakeWH(w / texture->width(),
h / texture->height());
diff --git a/src/gpu/GrSWMaskHelper.h b/src/gpu/GrSWMaskHelper.h
index c7807691d4..daf3111d55 100644
--- a/src/gpu/GrSWMaskHelper.h
+++ b/src/gpu/GrSWMaskHelper.h
@@ -9,7 +9,7 @@
#define GrSWMaskHelper_DEFINED
#include "GrColor.h"
-#include "GrMatrix.h"
+#include "SkMatrix.h"
#include "GrNoncopyable.h"
#include "SkBitmap.h"
#include "SkDraw.h"
@@ -21,6 +21,7 @@ class GrAutoScratchTexture;
class GrContext;
class GrTexture;
class SkPath;
+class SkStrokeRec;
class GrDrawTarget;
/**
@@ -47,15 +48,15 @@ public:
// may be accumulated in the helper during creation, "resultBounds"
// allows the caller to specify the region of interest - to limit the
// amount of work.
- bool init(const GrIRect& resultBounds, const GrMatrix* matrix);
+ bool init(const GrIRect& resultBounds, const SkMatrix* matrix);
// Draw a single rect into the accumulation bitmap using the specified op
void draw(const GrRect& rect, SkRegion::Op op,
bool antiAlias, uint8_t alpha);
// Draw a single path into the accumuation bitmap using the specified op
- void draw(const SkPath& path, SkRegion::Op op,
- GrPathFill fill, bool antiAlias, uint8_t alpha);
+ void draw(const SkPath& path, const SkStrokeRec& stroke, SkRegion::Op op,
+ bool antiAlias, uint8_t alpha);
// Helper function to get a scratch texture suitable for capturing the
// result (i.e., right size & format)
@@ -74,10 +75,10 @@ public:
// to the GPU. The result is returned in "result".
static GrTexture* DrawPathMaskToTexture(GrContext* context,
const SkPath& path,
+ const SkStrokeRec& stroke,
const GrIRect& resultBounds,
- GrPathFill fill,
bool antiAlias,
- GrMatrix* matrix);
+ SkMatrix* matrix);
// This utility routine is used to add a path's mask to some other draw.
// The ClipMaskManager uses it to accumulate clip masks while the
@@ -96,7 +97,7 @@ public:
protected:
private:
GrContext* fContext;
- GrMatrix fMatrix;
+ SkMatrix fMatrix;
SkBitmap fBM;
SkDraw fDraw;
SkRasterClip fRasterClip;
diff --git a/src/gpu/GrSoftwarePathRenderer.cpp b/src/gpu/GrSoftwarePathRenderer.cpp
index 3dd96061a5..36bdcff9ea 100644
--- a/src/gpu/GrSoftwarePathRenderer.cpp
+++ b/src/gpu/GrSoftwarePathRenderer.cpp
@@ -11,9 +11,9 @@
#include "GrSWMaskHelper.h"
////////////////////////////////////////////////////////////////////////////////
-bool GrSoftwarePathRenderer::canDrawPath(const SkPath& path,
- GrPathFill fill,
- const GrDrawTarget* target,
+bool GrSoftwarePathRenderer::canDrawPath(const SkPath&,
+ const SkStrokeRec&,
+ const GrDrawTarget*,
bool antiAlias) const {
if (!antiAlias || NULL == fContext) {
// TODO: We could allow the SW path to also handle non-AA paths but
@@ -28,6 +28,13 @@ bool GrSoftwarePathRenderer::canDrawPath(const SkPath& path,
return true;
}
+GrPathRenderer::StencilSupport GrSoftwarePathRenderer::onGetStencilSupport(
+ const SkPath&,
+ const SkStrokeRec&,
+ const GrDrawTarget*) const {
+ return GrPathRenderer::kNoSupport_StencilSupport;
+}
+
namespace {
////////////////////////////////////////////////////////////////////////////////
@@ -36,7 +43,7 @@ namespace {
// path bounds would be empty.
bool get_path_and_clip_bounds(const GrDrawTarget* target,
const SkPath& path,
- const GrMatrix& matrix,
+ const SkMatrix& matrix,
GrIRect* devPathBounds,
GrIRect* devClipBounds) {
// compute bounds as intersection of rt size, clip, and path
@@ -107,7 +114,7 @@ void draw_around_inv_path(GrDrawTarget* target,
////////////////////////////////////////////////////////////////////////////////
// return true on success; false on failure
bool GrSoftwarePathRenderer::onDrawPath(const SkPath& path,
- GrPathFill fill,
+ const SkStrokeRec& stroke,
GrDrawTarget* target,
bool antiAlias) {
@@ -117,20 +124,20 @@ bool GrSoftwarePathRenderer::onDrawPath(const SkPath& path,
GrDrawState* drawState = target->drawState();
- GrMatrix vm = drawState->getViewMatrix();
+ SkMatrix vm = drawState->getViewMatrix();
GrIRect devPathBounds, devClipBounds;
if (!get_path_and_clip_bounds(target, path, vm,
&devPathBounds, &devClipBounds)) {
- if (GrIsFillInverted(fill)) {
+ if (path.isInverseFillType()) {
draw_around_inv_path(target, devClipBounds, devPathBounds);
}
return true;
}
SkAutoTUnref<GrTexture> texture(
- GrSWMaskHelper::DrawPathMaskToTexture(fContext, path,
- devPathBounds, fill,
+ GrSWMaskHelper::DrawPathMaskToTexture(fContext, path, stroke,
+ devPathBounds,
antiAlias, &vm));
if (NULL == texture) {
return false;
@@ -138,7 +145,7 @@ bool GrSoftwarePathRenderer::onDrawPath(const SkPath& path,
GrSWMaskHelper::DrawToTargetWithPathMask(texture, target, devPathBounds);
- if (GrIsFillInverted(fill)) {
+ if (path.isInverseFillType()) {
draw_around_inv_path(target, devClipBounds, devPathBounds);
}
diff --git a/src/gpu/GrSoftwarePathRenderer.h b/src/gpu/GrSoftwarePathRenderer.h
index fdcc7bd46a..f8c5620f49 100644
--- a/src/gpu/GrSoftwarePathRenderer.h
+++ b/src/gpu/GrSoftwarePathRenderer.h
@@ -24,14 +24,18 @@ public:
: fContext(context) {
}
- virtual bool canDrawPath(const SkPath& path,
- GrPathFill fill,
- const GrDrawTarget* target,
- bool antiAlias) const SK_OVERRIDE;
+ virtual bool canDrawPath(const SkPath&,
+ const SkStrokeRec&,
+ const GrDrawTarget*,
+ bool antiAlias) const SK_OVERRIDE;
protected:
- virtual bool onDrawPath(const SkPath& path,
- GrPathFill fill,
- GrDrawTarget* target,
+ virtual StencilSupport onGetStencilSupport(const SkPath&,
+ const SkStrokeRec&,
+ const GrDrawTarget*) const SK_OVERRIDE;
+
+ virtual bool onDrawPath(const SkPath&,
+ const SkStrokeRec&,
+ GrDrawTarget*,
bool antiAlias) SK_OVERRIDE;
private:
diff --git a/src/gpu/GrStencilAndCoverPathRenderer.cpp b/src/gpu/GrStencilAndCoverPathRenderer.cpp
index a3f57cd9a2..44da3f9fd2 100644
--- a/src/gpu/GrStencilAndCoverPathRenderer.cpp
+++ b/src/gpu/GrStencilAndCoverPathRenderer.cpp
@@ -11,6 +11,7 @@
#include "GrContext.h"
#include "GrGpu.h"
#include "GrPath.h"
+#include "SkStrokeRec.h"
GrPathRenderer* GrStencilAndCoverPathRenderer::Create(GrContext* context) {
GrAssert(NULL != context);
@@ -33,52 +34,53 @@ GrStencilAndCoverPathRenderer::~GrStencilAndCoverPathRenderer() {
}
bool GrStencilAndCoverPathRenderer::canDrawPath(const SkPath& path,
- GrPathFill fill,
+ const SkStrokeRec& stroke,
const GrDrawTarget* target,
bool antiAlias) const {
- return kHairLine_GrPathFill != fill &&
+ return stroke.isFillStyle() &&
!antiAlias && // doesn't do per-path AA, relies on the target having MSAA
target->getDrawState().getStencil().isDisabled();
}
-bool GrStencilAndCoverPathRenderer::requiresStencilPass(const SkPath& path,
- GrPathFill fill,
- const GrDrawTarget* target) const {
- return true;
+GrPathRenderer::StencilSupport GrStencilAndCoverPathRenderer::onGetStencilSupport(
+ const SkPath&,
+ const SkStrokeRec& ,
+ const GrDrawTarget*) const {
+ return GrPathRenderer::kStencilOnly_StencilSupport;
}
-void GrStencilAndCoverPathRenderer::drawPathToStencil(const SkPath& path,
- GrPathFill fill,
- GrDrawTarget* target) {
- GrAssert(kEvenOdd_GrPathFill == fill || kWinding_GrPathFill == fill);
+void GrStencilAndCoverPathRenderer::onStencilPath(const SkPath& path,
+ const SkStrokeRec& stroke,
+ GrDrawTarget* target) {
+ GrAssert(!path.isInverseFillType());
SkAutoTUnref<GrPath> p(fGpu->createPath(path));
- target->stencilPath(p, fill);
+ target->stencilPath(p, stroke, path.getFillType());
}
bool GrStencilAndCoverPathRenderer::onDrawPath(const SkPath& path,
- GrPathFill fill,
+ const SkStrokeRec& stroke,
GrDrawTarget* target,
bool antiAlias) {
GrAssert(!antiAlias);
- GrAssert(kHairLine_GrPathFill != fill);
+ GrAssert(!stroke.isHairlineStyle());
GrDrawState* drawState = target->drawState();
GrAssert(drawState->getStencil().isDisabled());
SkAutoTUnref<GrPath> p(fGpu->createPath(path));
- GrPathFill nonInvertedFill = GrNonInvertedFill(fill);
- target->stencilPath(p, nonInvertedFill);
+ SkPath::FillType nonInvertedFill = SkPath::ConvertToNonInverseFillType(path.getFillType());
+ target->stencilPath(p, stroke, nonInvertedFill);
// TODO: Use built in cover operation rather than a rect draw. This will require making our
// fragment shaders be able to eat varyings generated by a matrix.
// fill the path, zero out the stencil
GrRect bounds = p->getBounds();
- GrScalar bloat = drawState->getViewMatrix().getMaxStretch() * GR_ScalarHalf;
+ SkScalar bloat = drawState->getViewMatrix().getMaxStretch() * SK_ScalarHalf;
GrDrawState::AutoDeviceCoordDraw adcd;
- if (nonInvertedFill == fill) {
+ if (nonInvertedFill == path.getFillType()) {
GR_STATIC_CONST_SAME_STENCIL(kStencilPass,
kZero_StencilOp,
kZero_StencilOp,
@@ -98,10 +100,10 @@ bool GrStencilAndCoverPathRenderer::onDrawPath(const SkPath& path,
0xffff,
0x0000,
0xffff);
- GrMatrix vmi;
+ SkMatrix vmi;
bounds.setLTRB(0, 0,
- GrIntToScalar(drawState->getRenderTarget()->width()),
- GrIntToScalar(drawState->getRenderTarget()->height()));
+ SkIntToScalar(drawState->getRenderTarget()->width()),
+ SkIntToScalar(drawState->getRenderTarget()->height()));
// mapRect through persp matrix may not be correct
if (!drawState->getViewMatrix().hasPerspective() && drawState->getViewInverse(&vmi)) {
vmi.mapRect(&bounds);
diff --git a/src/gpu/GrStencilAndCoverPathRenderer.h b/src/gpu/GrStencilAndCoverPathRenderer.h
index e44ddb26a7..9ebcec9858 100644
--- a/src/gpu/GrStencilAndCoverPathRenderer.h
+++ b/src/gpu/GrStencilAndCoverPathRenderer.h
@@ -21,31 +21,31 @@ class GrGpu;
class GrStencilAndCoverPathRenderer : public GrPathRenderer {
public:
- static GrPathRenderer* Create(GrContext* context);
+ static GrPathRenderer* Create(GrContext*);
virtual ~GrStencilAndCoverPathRenderer();
- virtual bool canDrawPath(const SkPath& path,
- GrPathFill fill,
- const GrDrawTarget* target,
+ virtual bool canDrawPath(const SkPath&,
+ const SkStrokeRec&,
+ const GrDrawTarget*,
bool antiAlias) const SK_OVERRIDE;
- virtual bool requiresStencilPass(const SkPath& path,
- GrPathFill fill,
- const GrDrawTarget* target) const SK_OVERRIDE;
-
- virtual void drawPathToStencil(const SkPath& path,
- GrPathFill fill,
- GrDrawTarget* target) SK_OVERRIDE;
-
protected:
- virtual bool onDrawPath(const SkPath& path,
- GrPathFill fill,
- GrDrawTarget* target,
+ virtual StencilSupport onGetStencilSupport(const SkPath&,
+ const SkStrokeRec&,
+ const GrDrawTarget*) const SK_OVERRIDE;
+
+ virtual bool onDrawPath(const SkPath&,
+ const SkStrokeRec&,
+ GrDrawTarget*,
bool antiAlias) SK_OVERRIDE;
+ virtual void onStencilPath(const SkPath&,
+ const SkStrokeRec&,
+ GrDrawTarget*) SK_OVERRIDE;
+
private:
- GrStencilAndCoverPathRenderer(GrGpu* gpu);
+ GrStencilAndCoverPathRenderer(GrGpu*);
GrGpu* fGpu;
diff --git a/src/gpu/GrStencilBuffer.h b/src/gpu/GrStencilBuffer.h
index 7439c761bc..e4e5190598 100644
--- a/src/gpu/GrStencilBuffer.h
+++ b/src/gpu/GrStencilBuffer.h
@@ -33,31 +33,22 @@ public:
int numSamples() const { return fSampleCnt; }
// called to note the last clip drawn to this buffer.
- void setLastClip(const GrClipData& clipData, int width, int height) {
- // the clip stack needs to be copied separately (and deeply) since
- // it could change beneath the stencil buffer
- fLastClipStack = *clipData.fClipStack;
- fLastClipData.fClipStack = &fLastClipStack;
- fLastClipData.fOrigin = clipData.fOrigin;
- fLastClipWidth = width;
- fLastClipHeight = height;
- GrAssert(width <= fWidth);
- GrAssert(height <= fHeight);
+ void setLastClip(int32_t clipStackGenID,
+ const SkIRect& clipSpaceRect,
+ const SkIPoint clipSpaceToStencilOffset) {
+ fLastClipStackGenID = clipStackGenID;
+ fLastClipStackRect = clipSpaceRect;
+ fLastClipSpaceOffset = clipSpaceToStencilOffset;
}
// called to determine if we have to render the clip into SB.
- bool mustRenderClip(const GrClipData& clipData, int width, int height) const {
- // The clip is in device space. That is it doesn't scale to fit a
- // smaller RT. It is just truncated on the right / bottom edges.
- // Note that this assumes that the viewport origin never moves within
- // the stencil buffer. This is valid today.
- return width > fLastClipWidth ||
- height > fLastClipHeight ||
- clipData != fLastClipData;
- }
-
- const GrClipData& getLastClip() const {
- return fLastClipData;
+ bool mustRenderClip(int32_t clipStackGenID,
+ const SkIRect& clipSpaceRect,
+ const SkIPoint clipSpaceToStencilOffset) const {
+ return SkClipStack::kInvalidGenID == clipStackGenID ||
+ fLastClipStackGenID != clipStackGenID ||
+ fLastClipSpaceOffset != clipSpaceToStencilOffset ||
+ !fLastClipStackRect.contains(clipSpaceRect);
}
// Places the sb in the cache. The cache takes a ref of the stencil buffer.
@@ -72,10 +63,8 @@ protected:
, fHeight(height)
, fBits(bits)
, fSampleCnt(sampleCnt)
- , fLastClipStack()
- , fLastClipData()
- , fLastClipWidth(-1)
- , fLastClipHeight(-1) {
+ , fLastClipStackGenID(SkClipStack::kInvalidGenID) {
+ fLastClipStackRect.setEmpty();
}
private:
@@ -85,10 +74,9 @@ private:
int fBits;
int fSampleCnt;
- SkClipStack fLastClipStack;
- GrClipData fLastClipData;
- int fLastClipWidth;
- int fLastClipHeight;
+ int32_t fLastClipStackGenID;
+ SkIRect fLastClipStackRect;
+ SkIPoint fLastClipSpaceOffset;
typedef GrResource INHERITED;
};
diff --git a/src/gpu/GrTextContext.cpp b/src/gpu/GrTextContext.cpp
index 59b9cb3fa0..fd7a6891e8 100644
--- a/src/gpu/GrTextContext.cpp
+++ b/src/gpu/GrTextContext.cpp
@@ -17,6 +17,7 @@
#include "GrTextStrike.h"
#include "GrTextStrike_impl.h"
#include "SkPath.h"
+#include "SkStrokeRec.h"
enum {
kGlyphMaskStage = GrPaint::kTotalStages,
@@ -29,12 +30,12 @@ void GrTextContext::flushGlyphs() {
GrDrawState* drawState = fDrawTarget->drawState();
if (fCurrVertex > 0) {
// setup our sampler state for our text texture/atlas
- drawState->sampler(kGlyphMaskStage)->reset();
+ drawState->stage(kGlyphMaskStage)->reset();
GrAssert(GrIsALIGN4(fCurrVertex));
GrAssert(fCurrTexture);
GrTextureParams params(SkShader::kRepeat_TileMode, false);
- drawState->createTextureEffect(kGlyphMaskStage, fCurrTexture, GrMatrix::I(), params);
+ drawState->createTextureEffect(kGlyphMaskStage, fCurrTexture, SkMatrix::I(), params);
if (!GrPixelConfigIsAlphaOnly(fCurrTexture->config())) {
if (kOne_GrBlendCoeff != fPaint.getSrcBlendCoeff() ||
@@ -131,8 +132,8 @@ void GrTextContext::drawPackedGlyph(GrGlyph::PackedID packed,
return;
}
- vx += GrIntToFixed(glyph->fBounds.fLeft);
- vy += GrIntToFixed(glyph->fBounds.fTop);
+ vx += SkIntToFixed(glyph->fBounds.fLeft);
+ vy += SkIntToFixed(glyph->fBounds.fTop);
// keep them as ints until we've done the clip-test
GrFixed width = glyph->fBounds.width();
@@ -143,7 +144,7 @@ void GrTextContext::drawPackedGlyph(GrGlyph::PackedID packed,
int x = vx >> 16;
int y = vy >> 16;
if (fClipRect.quickReject(x, y, x + width, y + height)) {
-// Gr_clz(3); // so we can set a break-point in the debugger
+// SkCLZ(3); // so we can set a break-point in the debugger
return;
}
}
@@ -174,12 +175,13 @@ void GrTextContext::drawPackedGlyph(GrGlyph::PackedID packed,
}
GrContext::AutoMatrix am;
- GrMatrix translate;
- translate.setTranslate(GrFixedToScalar(vx - GrIntToFixed(glyph->fBounds.fLeft)),
- GrFixedToScalar(vy - GrIntToFixed(glyph->fBounds.fTop)));
+ SkMatrix translate;
+ translate.setTranslate(SkFixedToScalar(vx - SkIntToFixed(glyph->fBounds.fLeft)),
+ SkFixedToScalar(vy - SkIntToFixed(glyph->fBounds.fTop)));
GrPaint tmpPaint(fPaint);
am.setPreConcat(fContext, translate, &tmpPaint);
- fContext->drawPath(tmpPaint, *glyph->fPath, kWinding_GrPathFill);
+ SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
+ fContext->drawPath(tmpPaint, *glyph->fPath, stroke);
return;
}
@@ -187,8 +189,8 @@ HAS_ATLAS:
GrAssert(glyph->fAtlas);
// now promote them to fixed
- width = GrIntToFixed(width);
- height = GrIntToFixed(height);
+ width = SkIntToFixed(width);
+ height = SkIntToFixed(height);
GrTexture* texture = glyph->fAtlas->texture();
GrAssert(texture);
@@ -234,8 +236,8 @@ HAS_ATLAS:
GrAlwaysAssert(success);
}
- GrFixed tx = GrIntToFixed(glyph->fAtlasLocation.fX);
- GrFixed ty = GrIntToFixed(glyph->fAtlasLocation.fY);
+ GrFixed tx = SkIntToFixed(glyph->fAtlasLocation.fX);
+ GrFixed ty = SkIntToFixed(glyph->fAtlasLocation.fY);
#if GR_TEXT_SCALAR_IS_USHORT
int x = vx >> 16;
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index 02bfdbd215..7b06d6080b 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -7,7 +7,6 @@
#include "SkGpuDevice.h"
-#include "effects/GrColorTableEffect.h"
#include "effects/GrTextureDomainEffect.h"
#include "GrContext.h"
@@ -20,6 +19,8 @@
#include "SkDrawProcs.h"
#include "SkGlyphCache.h"
#include "SkImageFilter.h"
+#include "SkPathEffect.h"
+#include "SkStroke.h"
#include "SkUtils.h"
#define CACHE_COMPATIBLE_DEVICE_TEXTURES 1
@@ -60,12 +61,12 @@ enum {
// a sub region of a larger source image.
#define COLOR_BLEED_TOLERANCE SkFloatToScalar(0.001f)
-#define DO_DEFERRED_CLEAR() \
- do { \
- if (fNeedClear) { \
- this->clear(0x0); \
- } \
- } while (false) \
+#define DO_DEFERRED_CLEAR() \
+ do { \
+ if (fNeedClear) { \
+ this->clear(SK_ColorTRANSPARENT); \
+ } \
+ } while (false) \
///////////////////////////////////////////////////////////////////////////////
@@ -205,7 +206,8 @@ void SkGpuDevice::initFromRenderTarget(GrContext* context,
SkGpuDevice::SkGpuDevice(GrContext* context,
SkBitmap::Config config,
int width,
- int height)
+ int height,
+ int sampleCount)
: SkDevice(config, width, height, false /*isOpaque*/) {
fDrawProcs = NULL;
@@ -219,14 +221,13 @@ SkGpuDevice::SkGpuDevice(GrContext* context,
if (config != SkBitmap::kRGB_565_Config) {
config = SkBitmap::kARGB_8888_Config;
}
- SkBitmap bm;
- bm.setConfig(config, width, height);
GrTextureDesc desc;
desc.fFlags = kRenderTarget_GrTextureFlagBit;
desc.fWidth = width;
desc.fHeight = height;
- desc.fConfig = SkBitmapConfig2GrPixelConfig(bm.config());
+ desc.fConfig = SkBitmapConfig2GrPixelConfig(config);
+ desc.fSampleCnt = sampleCount;
SkAutoTUnref<GrTexture> texture(fContext->createUncachedTexture(desc, NULL, 0));
@@ -257,6 +258,10 @@ SkGpuDevice::~SkGpuDevice() {
fContext->setRenderTarget(NULL);
}
+ if (fContext->getClip() == &fClipData) {
+ fContext->setClip(NULL);
+ }
+
SkSafeUnref(fRenderTarget);
fContext->unref();
}
@@ -438,7 +443,7 @@ SkGpuRenderTarget* SkGpuDevice::accessRenderTarget() {
bool SkGpuDevice::bindDeviceAsTexture(GrPaint* paint) {
GrTexture* texture = fRenderTarget->asTexture();
if (NULL != texture) {
- paint->colorSampler(kBitmapTextureIdx)->setCustomStage(
+ paint->colorStage(kBitmapTextureIdx)->setEffect(
SkNEW_ARGS(GrSingleTextureEffect, (texture)))->unref();
return true;
}
@@ -500,33 +505,34 @@ inline bool skPaint2GrPaintNoShader(SkGpuDevice* dev,
grPaint->setColor(SkColor2GrColor(skPaint.getColor()));
GrAssert(!grPaint->isColorStageEnabled(kShaderTextureIdx));
}
+
SkColorFilter* colorFilter = skPaint.getColorFilter();
- SkColor color;
- SkXfermode::Mode filterMode;
- SkScalar matrix[20];
- SkBitmap colorTransformTable;
- // TODO: SkColorFilter::asCustomStage()
- if (colorFilter != NULL && colorFilter->asColorMode(&color, &filterMode)) {
- if (!constantColor) {
- grPaint->setXfermodeColorFilter(filterMode, SkColor2GrColor(color));
- } else {
+ if (NULL != colorFilter) {
+ // if the source color is a constant then apply the filter here once rather than per pixel
+ // in a shader.
+ if (constantColor) {
SkColor filtered = colorFilter->filterColor(skPaint.getColor());
grPaint->setColor(SkColor2GrColor(filtered));
+ } else {
+ SkAutoTUnref<GrEffect> effect(colorFilter->asNewEffect(dev->context()));
+ if (NULL != effect.get()) {
+ grPaint->colorStage(kColorFilterTextureIdx)->setEffect(effect);
+ } else {
+ // TODO: rewrite this using asNewEffect()
+ SkColor color;
+ SkXfermode::Mode filterMode;
+ if (colorFilter->asColorMode(&color, &filterMode)) {
+ grPaint->setXfermodeColorFilter(filterMode, SkColor2GrColor(color));
+ }
+ }
}
- } else if (colorFilter != NULL && colorFilter->asColorMatrix(matrix)) {
- grPaint->setColorMatrix(matrix);
- } else if (colorFilter != NULL && colorFilter->asComponentTable(&colorTransformTable)) {
- // pass NULL because the color table effect doesn't use tiling or filtering.
- GrTexture* texture = act->set(dev, colorTransformTable, NULL);
- GrSamplerState* colorSampler = grPaint->colorSampler(kColorFilterTextureIdx);
- colorSampler->reset();
- colorSampler->setCustomStage(SkNEW_ARGS(GrColorTableEffect, (texture)))->unref();
}
+
return true;
}
// This function is similar to skPaint2GrPaintNoShader but also converts
-// skPaint's shader to a GrTexture/GrSamplerState if possible. The texture to
+// skPaint's shader to a GrTexture/GrEffectStage if possible. The texture to
// be used is set on grPaint and returned in param act. constantColor has the
// same meaning as in skPaint2GrPaintNoShader.
inline bool skPaint2GrPaintShader(SkGpuDevice* dev,
@@ -547,8 +553,8 @@ inline bool skPaint2GrPaintShader(SkGpuDevice* dev,
return false;
}
- GrSamplerState* sampler = grPaint->colorSampler(kShaderTextureIdx);
- if (shader->asNewCustomStage(dev->context(), sampler)) {
+ GrEffectStage* stage = grPaint->colorStage(kShaderTextureIdx);
+ if (shader->asNewEffect(dev->context(), stage)) {
return true;
}
@@ -580,6 +586,16 @@ inline bool skPaint2GrPaintShader(SkGpuDevice* dev,
return false;
}
+ // since our texture coords will be in local space, we whack the texture
+ // matrix to map them back into 0...1 before we load it
+ if (shader->hasLocalMatrix()) {
+ SkMatrix inverse;
+ if (!shader->getLocalMatrix().invert(&inverse)) {
+ return false;
+ }
+ matrix.preConcat(inverse);
+ }
+
// Must set wrap and filter on the sampler before requesting a texture.
GrTextureParams params(tileModes, skPaint.isFilterBitmap());
GrTexture* texture = textures[kShaderTextureIdx].set(dev, bitmap, &params);
@@ -589,21 +605,12 @@ inline bool skPaint2GrPaintShader(SkGpuDevice* dev,
return false;
}
- // since our texture coords will be in local space, we whack the texture
- // matrix to map them back into 0...1 before we load it
- SkMatrix localM;
- if (shader->getLocalMatrix(&localM)) {
- SkMatrix inverse;
- if (localM.invert(&inverse)) {
- matrix.preConcat(inverse);
- }
- }
if (SkShader::kDefault_BitmapType == bmptype) {
- GrScalar sx = SkFloatToScalar(1.f / bitmap.width());
- GrScalar sy = SkFloatToScalar(1.f / bitmap.height());
+ SkScalar sx = SkFloatToScalar(1.f / bitmap.width());
+ SkScalar sy = SkFloatToScalar(1.f / bitmap.height());
matrix.postScale(sx, sy);
}
- sampler->setCustomStage(SkNEW_ARGS(GrSingleTextureEffect, (texture, params)), matrix)->unref();
+ stage->setEffect(SkNEW_ARGS(GrSingleTextureEffect, (texture, matrix, params)))->unref();
return true;
}
@@ -611,7 +618,7 @@ inline bool skPaint2GrPaintShader(SkGpuDevice* dev,
///////////////////////////////////////////////////////////////////////////////
void SkGpuDevice::clear(SkColor color) {
- fContext->clear(NULL, color, fRenderTarget);
+ fContext->clear(NULL, SkColor2GrColor(color), fRenderTarget);
fNeedClear = false;
}
@@ -736,22 +743,6 @@ void SkGpuDevice::drawRect(const SkDraw& draw, const SkRect& rect,
// helpers for applying mask filters
namespace {
-GrPathFill skToGrFillType(SkPath::FillType fillType) {
- switch (fillType) {
- case SkPath::kWinding_FillType:
- return kWinding_GrPathFill;
- case SkPath::kEvenOdd_FillType:
- return kEvenOdd_GrPathFill;
- case SkPath::kInverseWinding_FillType:
- return kInverseWinding_GrPathFill;
- case SkPath::kInverseEvenOdd_FillType:
- return kInverseEvenOdd_GrPathFill;
- default:
- SkDebugf("Unsupported path fill type\n");
- return kHairLine_GrPathFill;
- }
-}
-
// We prefer to blur small rect with small radius via CPU.
#define MIN_GPU_BLUR_SIZE SkIntToScalar(64)
#define MIN_GPU_BLUR_RADIUS SkIntToScalar(32)
@@ -764,12 +755,9 @@ inline bool shouldDrawBlurWithCPU(const SkRect& rect, SkScalar radius) {
return false;
}
-bool drawWithGPUMaskFilter(GrContext* context, const SkPath& devPath,
+bool drawWithGPUMaskFilter(GrContext* context, const SkPath& devPath, const SkStrokeRec& stroke,
SkMaskFilter* filter, const SkRegion& clip,
- SkBounder* bounder, GrPaint* grp, GrPathFill pathFillType) {
-#ifdef SK_DISABLE_GPU_BLUR
- return false;
-#endif
+ SkBounder* bounder, GrPaint* grp) {
SkMaskFilter::BlurInfo info;
SkMaskFilter::BlurType blurType = filter->asABlur(&info);
if (SkMaskFilter::kNone_BlurType == blurType) {
@@ -849,25 +837,28 @@ bool drawWithGPUMaskFilter(GrContext* context, const SkPath& devPath,
GrContext::AutoMatrix am;
// Draw hard shadow to pathTexture with path top-left at origin using tempPaint.
- GrMatrix translate;
+ SkMatrix translate;
translate.setTranslate(offset.fX, offset.fY);
am.set(context, translate);
- context->drawPath(tempPaint, devPath, pathFillType);
+ context->drawPath(tempPaint, devPath, stroke);
// If we're doing a normal blur, we can clobber the pathTexture in the
// gaussianBlur. Otherwise, we need to save it for later compositing.
bool isNormalBlur = blurType == SkMaskFilter::kNormal_BlurType;
blurTexture.reset(context->gaussianBlur(pathTexture, isNormalBlur,
srcRect, sigma, sigma));
+ if (NULL == blurTexture) {
+ return false;
+ }
if (!isNormalBlur) {
context->setIdentityMatrix();
GrPaint paint;
- GrMatrix matrix;
+ SkMatrix matrix;
matrix.setIDiv(pathTexture->width(), pathTexture->height());
// Blend pathTexture over blurTexture.
context->setRenderTarget(blurTexture->asRenderTarget());
- paint.colorSampler(0)->setCustomStage(SkNEW_ARGS(GrSingleTextureEffect, (pathTexture)), matrix)->unref();
+ paint.colorStage(0)->setEffect(SkNEW_ARGS(GrSingleTextureEffect, (pathTexture, matrix)))->unref();
if (SkMaskFilter::kInner_BlurType == blurType) {
// inner: dst = dst * src
paint.setBlendFunc(kDC_GrBlendCoeff, kZero_GrBlendCoeff);
@@ -893,12 +884,12 @@ bool drawWithGPUMaskFilter(GrContext* context, const SkPath& devPath,
// we assume the last mask index is available for use
GrAssert(!grp->isCoverageStageEnabled(MASK_IDX));
- GrMatrix matrix;
+ SkMatrix matrix;
matrix.setTranslate(-finalRect.fLeft, -finalRect.fTop);
matrix.postIDiv(blurTexture->width(), blurTexture->height());
- grp->coverageSampler(MASK_IDX)->reset();
- grp->coverageSampler(MASK_IDX)->setCustomStage(SkNEW_ARGS(GrSingleTextureEffect, (blurTexture)), matrix)->unref();
+ grp->coverageStage(MASK_IDX)->reset();
+ grp->coverageStage(MASK_IDX)->setEffect(SkNEW_ARGS(GrSingleTextureEffect, (blurTexture, matrix)))->unref();
context->drawRect(*grp, finalRect);
return true;
}
@@ -950,16 +941,16 @@ bool drawWithMaskFilter(GrContext* context, const SkPath& devPath,
// we assume the last mask index is available for use
GrAssert(!grp->isCoverageStageEnabled(MASK_IDX));
- GrMatrix m;
+ SkMatrix m;
m.setTranslate(-dstM.fBounds.fLeft*SK_Scalar1, -dstM.fBounds.fTop*SK_Scalar1);
m.postIDiv(texture->width(), texture->height());
- grp->coverageSampler(MASK_IDX)->setCustomStage(SkNEW_ARGS(GrSingleTextureEffect, (texture)), m)->unref();
+ grp->coverageStage(MASK_IDX)->setEffect(SkNEW_ARGS(GrSingleTextureEffect, (texture, m)))->unref();
GrRect d;
- d.setLTRB(GrIntToScalar(dstM.fBounds.fLeft),
- GrIntToScalar(dstM.fBounds.fTop),
- GrIntToScalar(dstM.fBounds.fRight),
- GrIntToScalar(dstM.fBounds.fBottom));
+ d.setLTRB(SkIntToScalar(dstM.fBounds.fLeft),
+ SkIntToScalar(dstM.fBounds.fTop),
+ SkIntToScalar(dstM.fBounds.fRight),
+ SkIntToScalar(dstM.fBounds.fBottom));
context->drawRect(*grp, d);
return true;
@@ -975,8 +966,6 @@ void SkGpuDevice::drawPath(const SkDraw& draw, const SkPath& origSrcPath,
CHECK_FOR_NODRAW_ANNOTATION(paint);
CHECK_SHOULD_DRAW(draw, false);
- bool doFill = true;
-
GrPaint grPaint;
SkAutoCachedTexture textures[GrPaint::kMaxColorStages];
if (!skPaint2GrPaintShader(this,
@@ -990,8 +979,8 @@ void SkGpuDevice::drawPath(const SkDraw& draw, const SkPath& origSrcPath,
// can we cheat, and threat a thin stroke as a hairline w/ coverage
// if we can, we draw lots faster (raster device does this same test)
SkScalar hairlineCoverage;
- if (SkDrawTreatAsHairline(paint, fContext->getMatrix(), &hairlineCoverage)) {
- doFill = false;
+ bool doHairLine = SkDrawTreatAsHairline(paint, fContext->getMatrix(), &hairlineCoverage);
+ if (doHairLine) {
grPaint.setCoverage(SkScalarRoundToInt(hairlineCoverage * grPaint.getCoverage()));
}
@@ -999,7 +988,7 @@ void SkGpuDevice::drawPath(const SkDraw& draw, const SkPath& origSrcPath,
// where the original path can in fact be modified in place (even though
// its parameter type is const).
SkPath* pathPtr = const_cast<SkPath*>(&origSrcPath);
- SkPath tmpPath;
+ SkPath tmpPath, effectPath;
if (prePathMatrix) {
SkPath* result = pathPtr;
@@ -1016,56 +1005,40 @@ void SkGpuDevice::drawPath(const SkDraw& draw, const SkPath& origSrcPath,
// at this point we're done with prePathMatrix
SkDEBUGCODE(prePathMatrix = (const SkMatrix*)0x50FF8001;)
- if (paint.getPathEffect() ||
- (doFill && paint.getStyle() != SkPaint::kFill_Style)) {
- // it is safe to use tmpPath here, even if we already used it for the
- // prepathmatrix, since getFillPath can take the same object for its
- // input and output safely.
- doFill = paint.getFillPath(*pathPtr, &tmpPath);
- pathPtr = &tmpPath;
+ SkStrokeRec stroke(paint);
+ SkPathEffect* pathEffect = paint.getPathEffect();
+ if (pathEffect && pathEffect->filterPath(&effectPath, *pathPtr, &stroke)) {
+ pathPtr = &effectPath;
+ }
+
+ if (!pathEffect && doHairLine) {
+ stroke.setHairlineStyle();
}
if (paint.getMaskFilter()) {
+ if (!stroke.isHairlineStyle()) {
+ if (stroke.applyToPath(&tmpPath, *pathPtr)) {
+ pathPtr = &tmpPath;
+ stroke.setFillStyle();
+ }
+ }
+
// avoid possibly allocating a new path in transform if we can
SkPath* devPathPtr = pathIsMutable ? pathPtr : &tmpPath;
// transform the path into device space
pathPtr->transform(fContext->getMatrix(), devPathPtr);
- GrPathFill pathFillType = doFill ?
- skToGrFillType(devPathPtr->getFillType()) : kHairLine_GrPathFill;
- if (!drawWithGPUMaskFilter(fContext, *devPathPtr, paint.getMaskFilter(),
- *draw.fClip, draw.fBounder, &grPaint, pathFillType)) {
- SkPaint::Style style = doFill ? SkPaint::kFill_Style :
- SkPaint::kStroke_Style;
+ if (!drawWithGPUMaskFilter(fContext, *devPathPtr, stroke, paint.getMaskFilter(),
+ *draw.fClip, draw.fBounder, &grPaint)) {
+ SkPaint::Style style = stroke.isHairlineStyle() ? SkPaint::kStroke_Style :
+ SkPaint::kFill_Style;
drawWithMaskFilter(fContext, *devPathPtr, paint.getMaskFilter(),
*draw.fClip, draw.fBounder, &grPaint, style);
}
return;
}
- GrPathFill fill = kHairLine_GrPathFill;
-
- if (doFill) {
- switch (pathPtr->getFillType()) {
- case SkPath::kWinding_FillType:
- fill = kWinding_GrPathFill;
- break;
- case SkPath::kEvenOdd_FillType:
- fill = kEvenOdd_GrPathFill;
- break;
- case SkPath::kInverseWinding_FillType:
- fill = kInverseWinding_GrPathFill;
- break;
- case SkPath::kInverseEvenOdd_FillType:
- fill = kInverseEvenOdd_GrPathFill;
- break;
- default:
- SkDebugf("Unsupported path fill type\n");
- return;
- }
- }
-
- fContext->drawPath(grPaint, *pathPtr, fill);
+ fContext->drawPath(grPaint, *pathPtr, stroke);
}
namespace {
@@ -1367,7 +1340,7 @@ void SkGpuDevice::internalDrawBitmap(const SkBitmap& bitmap,
return;
}
- GrSamplerState* sampler = grPaint->colorSampler(kBitmapTextureIdx);
+ GrEffectStage* stage = grPaint->colorStage(kBitmapTextureIdx);
GrTexture* texture;
SkAutoCachedTexture act(this, bitmap, &params, &texture);
@@ -1408,50 +1381,52 @@ void SkGpuDevice::internalDrawBitmap(const SkBitmap& bitmap,
}
GrRect textureDomain = GrRect::MakeEmpty();
- SkAutoTUnref<GrCustomStage> stage;
+ SkAutoTUnref<GrEffect> effect;
if (needsTextureDomain) {
// Use a constrained texture domain to avoid color bleeding
- GrScalar left, top, right, bottom;
- if (srcRect.width() > GR_Scalar1) {
- GrScalar border = GR_ScalarHalf / bitmap.width();
+ SkScalar left, top, right, bottom;
+ if (srcRect.width() > SK_Scalar1) {
+ SkScalar border = SK_ScalarHalf / bitmap.width();
left = paintRect.left() + border;
right = paintRect.right() - border;
} else {
- left = right = GrScalarHalf(paintRect.left() + paintRect.right());
+ left = right = SkScalarHalf(paintRect.left() + paintRect.right());
}
- if (srcRect.height() > GR_Scalar1) {
- GrScalar border = GR_ScalarHalf / bitmap.height();
+ if (srcRect.height() > SK_Scalar1) {
+ SkScalar border = SK_ScalarHalf / bitmap.height();
top = paintRect.top() + border;
bottom = paintRect.bottom() - border;
} else {
- top = bottom = GrScalarHalf(paintRect.top() + paintRect.bottom());
+ top = bottom = SkScalarHalf(paintRect.top() + paintRect.bottom());
}
textureDomain.setLTRB(left, top, right, bottom);
- stage.reset(SkNEW_ARGS(GrTextureDomainEffect, (texture, textureDomain, params)));
+ effect.reset(GrTextureDomainEffect::Create(texture,
+ SkMatrix::I(),
+ textureDomain,
+ GrTextureDomainEffect::kClamp_WrapMode,
+ params.isBilerp()));
} else {
- stage.reset(SkNEW_ARGS(GrSingleTextureEffect, (texture, params)));
+ effect.reset(SkNEW_ARGS(GrSingleTextureEffect, (texture, params)));
}
- grPaint->colorSampler(kBitmapTextureIdx)->setCustomStage(stage);
+ grPaint->colorStage(kBitmapTextureIdx)->setEffect(effect);
fContext->drawRectToRect(*grPaint, dstRect, paintRect, &m);
}
namespace {
-void apply_custom_stage(GrContext* context,
- GrTexture* srcTexture,
- GrTexture* dstTexture,
- const GrRect& rect,
- GrCustomStage* stage) {
+void apply_effect(GrContext* context,
+ GrTexture* srcTexture,
+ GrTexture* dstTexture,
+ const GrRect& rect,
+ GrEffect* effect) {
SkASSERT(srcTexture && srcTexture->getContext() == context);
GrContext::AutoMatrix am;
am.setIdentity(context);
GrContext::AutoRenderTarget art(context, dstTexture->asRenderTarget());
GrContext::AutoClip acs(context, rect);
- GrMatrix sampleM;
- sampleM.setIDiv(srcTexture->width(), srcTexture->height());
GrPaint paint;
- paint.colorSampler(0)->setCustomStage(stage, sampleM);
+ paint.colorStage(0)->setEffect(effect);
context->drawRect(paint, rect);
}
@@ -1468,15 +1443,18 @@ static GrTexture* filter_texture(SkDevice* device, GrContext* context,
desc.fWidth = SkScalarCeilToInt(rect.width());
desc.fHeight = SkScalarCeilToInt(rect.height());
desc.fConfig = kRGBA_8888_GrPixelConfig;
- GrCustomStage* stage;
+ GrEffect* effect;
if (filter->canFilterImageGPU()) {
+ // Save the render target and set it to NULL, so we don't accidentally draw to it in the
+ // filter. Also set the clip wide open and the matrix to identity.
+ GrContext::AutoWideOpenIdentityDraw awo(context, NULL);
texture = filter->onFilterImageGPU(&proxy, texture, rect);
- } else if (filter->asNewCustomStage(&stage, texture)) {
+ } else if (filter->asNewEffect(&effect, texture)) {
GrAutoScratchTexture dst(context, desc);
- apply_custom_stage(context, texture, dst.texture(), rect, stage);
+ apply_effect(context, texture, dst.texture(), rect, effect);
texture = dst.detach();
- stage->unref();
+ effect->unref();
}
return texture;
}
@@ -1500,13 +1478,13 @@ void SkGpuDevice::drawSprite(const SkDraw& draw, const SkBitmap& bitmap,
return;
}
- GrSamplerState* sampler = grPaint.colorSampler(kBitmapTextureIdx);
+ GrEffectStage* stage = grPaint.colorStage(kBitmapTextureIdx);
GrTexture* texture;
- sampler->reset();
+ stage->reset();
// draw sprite uses the default texture params
SkAutoCachedTexture act(this, bitmap, NULL, &texture);
- grPaint.colorSampler(kBitmapTextureIdx)->setCustomStage(SkNEW_ARGS
+ grPaint.colorStage(kBitmapTextureIdx)->setEffect(SkNEW_ARGS
(GrSingleTextureEffect, (texture)))->unref();
SkImageFilter* filter = paint.getImageFilter();
@@ -1514,7 +1492,7 @@ void SkGpuDevice::drawSprite(const SkDraw& draw, const SkBitmap& bitmap,
GrTexture* filteredTexture = filter_texture(this, fContext, texture, filter,
GrRect::MakeWH(SkIntToScalar(w), SkIntToScalar(h)));
if (filteredTexture) {
- grPaint.colorSampler(kBitmapTextureIdx)->setCustomStage(SkNEW_ARGS
+ grPaint.colorStage(kBitmapTextureIdx)->setEffect(SkNEW_ARGS
(GrSingleTextureEffect, (filteredTexture)))->unref();
texture = filteredTexture;
filteredTexture->unref();
@@ -1522,12 +1500,12 @@ void SkGpuDevice::drawSprite(const SkDraw& draw, const SkBitmap& bitmap,
}
fContext->drawRectToRect(grPaint,
- GrRect::MakeXYWH(GrIntToScalar(left),
- GrIntToScalar(top),
- GrIntToScalar(w),
- GrIntToScalar(h)),
- GrRect::MakeWH(GR_Scalar1 * w / texture->width(),
- GR_Scalar1 * h / texture->height()));
+ GrRect::MakeXYWH(SkIntToScalar(left),
+ SkIntToScalar(top),
+ SkIntToScalar(w),
+ SkIntToScalar(h)),
+ GrRect::MakeWH(SK_Scalar1 * w / texture->width(),
+ SK_Scalar1 * h / texture->height()));
}
void SkGpuDevice::drawBitmapRect(const SkDraw& draw, const SkBitmap& bitmap,
@@ -1574,13 +1552,13 @@ void SkGpuDevice::drawDevice(const SkDraw& draw, SkDevice* device,
GrPaint grPaint;
SkAutoCachedTexture colorLutTexture;
- grPaint.colorSampler(kBitmapTextureIdx)->reset();
+ grPaint.colorStage(kBitmapTextureIdx)->reset();
if (!dev->bindDeviceAsTexture(&grPaint) ||
!skPaint2GrPaintNoShader(this, paint, true, false, &colorLutTexture, &grPaint)) {
return;
}
- GrTexture* devTex = grPaint.getColorSampler(kBitmapTextureIdx).getCustomStage()->texture(0);
+ GrTexture* devTex = grPaint.getColorStage(kBitmapTextureIdx).getEffect()->texture(0);
SkASSERT(NULL != devTex);
SkImageFilter* filter = paint.getImageFilter();
@@ -1589,7 +1567,7 @@ void SkGpuDevice::drawDevice(const SkDraw& draw, SkDevice* device,
SkIntToScalar(devTex->height()));
GrTexture* filteredTexture = filter_texture(this, fContext, devTex, filter, rect);
if (filteredTexture) {
- grPaint.colorSampler(kBitmapTextureIdx)->setCustomStage(SkNEW_ARGS
+ grPaint.colorStage(kBitmapTextureIdx)->setEffect(SkNEW_ARGS
(GrSingleTextureEffect, (filteredTexture)))->unref();
devTex = filteredTexture;
filteredTexture->unref();
@@ -1600,21 +1578,21 @@ void SkGpuDevice::drawDevice(const SkDraw& draw, SkDevice* device,
int w = bm.width();
int h = bm.height();
- GrRect dstRect = GrRect::MakeXYWH(GrIntToScalar(x),
- GrIntToScalar(y),
- GrIntToScalar(w),
- GrIntToScalar(h));
+ GrRect dstRect = GrRect::MakeXYWH(SkIntToScalar(x),
+ SkIntToScalar(y),
+ SkIntToScalar(w),
+ SkIntToScalar(h));
// The device being drawn may not fill up its texture (saveLayer uses
// the approximate ).
- GrRect srcRect = GrRect::MakeWH(GR_Scalar1 * w / devTex->width(),
- GR_Scalar1 * h / devTex->height());
+ GrRect srcRect = GrRect::MakeWH(SK_Scalar1 * w / devTex->width(),
+ SK_Scalar1 * h / devTex->height());
fContext->drawRectToRect(grPaint, dstRect, srcRect);
}
bool SkGpuDevice::canHandleImageFilter(SkImageFilter* filter) {
- if (!filter->asNewCustomStage(NULL, NULL) &&
+ if (!filter->asNewEffect(NULL, NULL) &&
!filter->canFilterImageGPU()) {
return false;
}
diff --git a/src/gpu/SkGrPixelRef.cpp b/src/gpu/SkGrPixelRef.cpp
index e770ef09b6..081c09f91c 100644
--- a/src/gpu/SkGrPixelRef.cpp
+++ b/src/gpu/SkGrPixelRef.cpp
@@ -48,8 +48,8 @@ bool SkROLockPixelsPixelRef::onLockPixelsAreWritable() const {
///////////////////////////////////////////////////////////////////////////////
-static SkGrPixelRef* copyToTexturePixelRef(GrTexture* texture,
- SkBitmap::Config dstConfig) {
+static SkGrPixelRef* copyToTexturePixelRef(GrTexture* texture, SkBitmap::Config dstConfig,
+ const SkIRect* subset) {
if (NULL == texture) {
return NULL;
}
@@ -59,8 +59,20 @@ static SkGrPixelRef* copyToTexturePixelRef(GrTexture* texture,
}
GrTextureDesc desc;
- desc.fWidth = texture->width();
- desc.fHeight = texture->height();
+ SkIPoint pointStorage;
+ SkIPoint* topLeft;
+ if (subset != NULL) {
+ SkASSERT(SkIRect::MakeWH(texture->width(), texture->height()).contains(*subset));
+ // Create a new texture that is the size of subset.
+ desc.fWidth = subset->width();
+ desc.fHeight = subset->height();
+ pointStorage.set(subset->x(), subset->y());
+ topLeft = &pointStorage;
+ } else {
+ desc.fWidth = texture->width();
+ desc.fHeight = texture->height();
+ topLeft = NULL;
+ }
desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
desc.fConfig = SkBitmapConfig2GrPixelConfig(dstConfig);
@@ -69,7 +81,7 @@ static SkGrPixelRef* copyToTexturePixelRef(GrTexture* texture,
return NULL;
}
- context->copyTexture(texture, dst->asRenderTarget());
+ context->copyTexture(texture, dst->asRenderTarget(), topLeft);
// TODO: figure out if this is responsible for Chrome canvas errors
#if 0
@@ -123,7 +135,7 @@ SkGpuTexture* SkGrPixelRef::getTexture() {
return NULL;
}
-SkPixelRef* SkGrPixelRef::deepCopy(SkBitmap::Config dstConfig) {
+SkPixelRef* SkGrPixelRef::deepCopy(SkBitmap::Config dstConfig, const SkIRect* subset) {
if (NULL == fSurface) {
return NULL;
}
@@ -134,7 +146,7 @@ SkPixelRef* SkGrPixelRef::deepCopy(SkBitmap::Config dstConfig) {
// a GrTexture owned elsewhere (e.g., SkGpuDevice), and cannot live
// independently of that texture. Texture-backed pixel refs, on the other
// hand, own their GrTextures, and are thus self-contained.
- return copyToTexturePixelRef(fSurface->asTexture(), dstConfig);
+ return copyToTexturePixelRef(fSurface->asTexture(), dstConfig, subset);
}
bool SkGrPixelRef::onReadPixels(SkBitmap* dst, const SkIRect* subset) {
@@ -155,7 +167,10 @@ bool SkGrPixelRef::onReadPixels(SkBitmap* dst, const SkIRect* subset) {
height = fSurface->height();
}
dst->setConfig(SkBitmap::kARGB_8888_Config, width, height);
- dst->allocPixels();
+ if (!dst->allocPixels()) {
+ SkDebugf("SkGrPixelRef::onReadPixels failed to alloc bitmap for result!\n");
+ return false;
+ }
SkAutoLockPixels al(*dst);
void* buffer = dst->getPixels();
return fSurface->readPixels(left, top, width, height,
diff --git a/src/gpu/effects/Gr1DKernelEffect.h b/src/gpu/effects/Gr1DKernelEffect.h
index 9ef765268c..171273360b 100644
--- a/src/gpu/effects/Gr1DKernelEffect.h
+++ b/src/gpu/effects/Gr1DKernelEffect.h
@@ -9,6 +9,7 @@
#define Gr1DKernelEffect_DEFINED
#include "GrSingleTextureEffect.h"
+#include "SkMatrix.h"
/**
* Base class for 1D kernel effects. The kernel operates either in X or Y and
@@ -18,6 +19,7 @@
* read. Since the center pixel is also read, the total width is one larger than
* two times the radius.
*/
+
class Gr1DKernelEffect : public GrSingleTextureEffect {
public:
@@ -29,7 +31,7 @@ public:
Gr1DKernelEffect(GrTexture* texture,
Direction direction,
int radius)
- : GrSingleTextureEffect(texture)
+ : GrSingleTextureEffect(texture, MakeDivByTextureWHMatrix(texture))
, fDirection(direction)
, fRadius(radius) {}
diff --git a/src/gpu/effects/GrColorTableEffect.cpp b/src/gpu/effects/GrColorTableEffect.cpp
deleted file mode 100644
index 165cd60443..0000000000
--- a/src/gpu/effects/GrColorTableEffect.cpp
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright 2012 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
- #include "GrColorTableEffect.h"
- #include "gl/GrGLProgramStage.h"
- #include "GrProgramStageFactory.h"
- #include "SkString.h"
-
-///////////////////////////////////////////////////////////////////////////////
-
-class GrGLColorTableEffect : public GrGLProgramStage {
-public:
- GrGLColorTableEffect(const GrProgramStageFactory& factory,
- const GrCustomStage& stage);
-
- virtual void setupVariables(GrGLShaderBuilder* state) SK_OVERRIDE {}
- virtual void emitVS(GrGLShaderBuilder* state,
- const char* vertexCoords) SK_OVERRIDE {}
- virtual void emitFS(GrGLShaderBuilder* state,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray&) SK_OVERRIDE;
-
- virtual void setData(const GrGLUniformManager&,
- const GrCustomStage&,
- const GrRenderTarget*,
- int stageNum) SK_OVERRIDE {}
-
- static StageKey GenKey(const GrCustomStage&, const GrGLCaps&);
-
-private:
-
- typedef GrGLProgramStage INHERITED;
-};
-
-GrGLColorTableEffect::GrGLColorTableEffect(
- const GrProgramStageFactory& factory, const GrCustomStage& stage)
- : INHERITED(factory) {
- }
-
-void GrGLColorTableEffect::emitFS(GrGLShaderBuilder* builder,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray& samplers) {
- static const float kColorScaleFactor = 255.0f / 256.0f;
- static const float kColorOffsetFactor = 1.0f / 512.0f;
- SkString* code = &builder->fFSCode;
- if (NULL == inputColor) {
- // the input color is solid white (all ones).
- static const float kMaxValue = kColorScaleFactor + kColorOffsetFactor;
- code->appendf("\t\tvec4 coord = vec4(%f, %f, %f, %f);\n",
- kMaxValue, kMaxValue, kMaxValue, kMaxValue);
-
- } else {
- code->appendf("\t\tvec4 coord = vec4(%s.rgb / %s.a, %s.a);\n",
- inputColor, inputColor, inputColor);
- code->appendf("\t\tcoord = coord * %f + vec4(%f, %f, %f, %f);\n",
- kColorScaleFactor,
- kColorOffsetFactor, kColorOffsetFactor,
- kColorOffsetFactor, kColorOffsetFactor);
- }
-
- code->appendf("\t\t%s.a = ", outputColor);
- builder->appendTextureLookup(code, samplers[0], "vec2(coord.a, 0.125)");
- code->append(";\n");
-
- code->appendf("\t\t%s.r = ", outputColor);
- builder->appendTextureLookup(code, samplers[0], "vec2(coord.r, 0.375)");
- code->append(";\n");
-
- code->appendf("\t\t%s.g = ", outputColor);
- builder->appendTextureLookup(code, samplers[0], "vec2(coord.g, 0.625)");
- code->append(";\n");
-
- code->appendf("\t\t%s.b = ", outputColor);
- builder->appendTextureLookup(code, samplers[0], "vec2(coord.b, 0.875)");
- code->append(";\n");
-
- code->appendf("\t\t%s.rgb *= %s.a;\n", outputColor, outputColor);
-}
-
-GrGLProgramStage::StageKey GrGLColorTableEffect::GenKey(const GrCustomStage& s,
- const GrGLCaps& caps) {
- return 0;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
-GrColorTableEffect::GrColorTableEffect(GrTexture* texture)
- : INHERITED(1)
- , fTextureAccess(texture, "a") {
-}
-
-GrColorTableEffect::~GrColorTableEffect() {
-}
-
-const GrProgramStageFactory& GrColorTableEffect::getFactory() const {
- return GrTProgramStageFactory<GrColorTableEffect>::getInstance();
-}
-
-bool GrColorTableEffect::isEqual(const GrCustomStage& sBase) const {
- return INHERITED::isEqual(sBase);
-}
-
-const GrTextureAccess& GrColorTableEffect::textureAccess(int index) const {
- GrAssert(0 == index);
- return fTextureAccess;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
-GR_DEFINE_CUSTOM_STAGE_TEST(GrColorTableEffect);
-
-GrCustomStage* GrColorTableEffect::TestCreate(SkRandom* random,
- GrContext* context,
- GrTexture* textures[]) {
- return SkNEW_ARGS(GrColorTableEffect, (textures[GrCustomStageUnitTest::kAlphaTextureIdx]));
-}
diff --git a/src/gpu/effects/GrColorTableEffect.h b/src/gpu/effects/GrColorTableEffect.h
deleted file mode 100644
index c064600c26..0000000000
--- a/src/gpu/effects/GrColorTableEffect.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2012 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef GrColorTableEffect_DEFINED
-#define GrColorTableEffect_DEFINED
-
-#include "GrSingleTextureEffect.h"
-#include "GrTexture.h"
-
-class GrGLColorTableEffect;
-
-/**
- * LUT-based color transformation effect. This class implements the Gr
- * counterpart to the SkTable_ColorFilter effect. A 256 * 4 (single-channel)
- * LUT is used to transform the input colors of the image.
- */
-class GrColorTableEffect : public GrCustomStage {
-public:
-
- explicit GrColorTableEffect(GrTexture* texture);
- virtual ~GrColorTableEffect();
-
- static const char* Name() { return "ColorTable"; }
- virtual const GrProgramStageFactory& getFactory() const SK_OVERRIDE;
- virtual bool isEqual(const GrCustomStage&) const SK_OVERRIDE;
-
- virtual const GrTextureAccess& textureAccess(int index) const SK_OVERRIDE;
-
- typedef GrGLColorTableEffect GLProgramStage;
-
-private:
- GR_DECLARE_CUSTOM_STAGE_TEST;
-
- GrTextureAccess fTextureAccess;
-
- typedef GrCustomStage INHERITED;
-};
-#endif
diff --git a/src/gpu/effects/GrConfigConversionEffect.cpp b/src/gpu/effects/GrConfigConversionEffect.cpp
index b9c9f63ae3..d42896d210 100644
--- a/src/gpu/effects/GrConfigConversionEffect.cpp
+++ b/src/gpu/effects/GrConfigConversionEffect.cpp
@@ -6,25 +6,32 @@
*/
#include "GrConfigConversionEffect.h"
-#include "gl/GrGLProgramStage.h"
+#include "GrContext.h"
+#include "GrTBackendEffectFactory.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
+#include "SkMatrix.h"
-class GrGLConfigConversionEffect : public GrGLProgramStage {
+class GrGLConfigConversionEffect : public GrGLEffect {
public:
- GrGLConfigConversionEffect(const GrProgramStageFactory& factory,
- const GrCustomStage& s) : INHERITED (factory) {
- const GrConfigConversionEffect& stage = static_cast<const GrConfigConversionEffect&>(s);
- fSwapRedAndBlue = stage.swapsRedAndBlue();
- fPMConversion = stage.pmConversion();
+ GrGLConfigConversionEffect(const GrBackendEffectFactory& factory,
+ const GrEffect& s) : INHERITED (factory) {
+ const GrConfigConversionEffect& effect = static_cast<const GrConfigConversionEffect&>(s);
+ fSwapRedAndBlue = effect.swapsRedAndBlue();
+ fPMConversion = effect.pmConversion();
}
- virtual void emitVS(GrGLShaderBuilder* builder,
- const char* vertexCoords) SK_OVERRIDE { }
- virtual void emitFS(GrGLShaderBuilder* builder,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray& samplers) SK_OVERRIDE {
+ virtual void emitCode(GrGLShaderBuilder* builder,
+ const GrEffectStage&,
+ EffectKey key,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) SK_OVERRIDE {
+ const char* coords;
+ GrSLType coordsType = fEffectMatrix.emitCode(builder, key, vertexCoords, &coords);
builder->fFSCode.appendf("\t\t%s = ", outputColor);
- builder->appendTextureLookup(&builder->fFSCode, samplers[0]);
+ builder->appendTextureLookup(&builder->fFSCode, samplers[0], coords, coordsType);
builder->fFSCode.append(";\n");
if (GrConfigConversionEffect::kNone_PMConversion == fPMConversion) {
GrAssert(fSwapRedAndBlue);
@@ -58,16 +65,34 @@ public:
GrGLSLMulVarBy4f(&builder->fFSCode, 2, outputColor, inputColor);
}
- static inline StageKey GenKey(const GrCustomStage& s, const GrGLCaps&) {
- const GrConfigConversionEffect& stage = static_cast<const GrConfigConversionEffect&>(s);
- return static_cast<int>(stage.swapsRedAndBlue()) | (stage.pmConversion() << 1);
+ void setData(const GrGLUniformManager& uman, const GrEffectStage& stage) {
+ const GrConfigConversionEffect& effect =
+ static_cast<const GrConfigConversionEffect&>(*stage.getEffect());
+ fEffectMatrix.setData(uman,
+ effect.getMatrix(),
+ stage.getCoordChangeMatrix(),
+ effect.texture(0));
+ }
+
+ static inline EffectKey GenKey(const GrEffectStage& s, const GrGLCaps&) {
+ const GrConfigConversionEffect& effect =
+ static_cast<const GrConfigConversionEffect&>(*s.getEffect());
+ EffectKey key = static_cast<EffectKey>(effect.swapsRedAndBlue()) |
+ (effect.pmConversion() << 1);
+ key <<= GrGLEffectMatrix::kKeyBits;
+ EffectKey matrixKey = GrGLEffectMatrix::GenKey(effect.getMatrix(),
+ s.getCoordChangeMatrix(),
+ effect.texture(0));
+ GrAssert(!(matrixKey & key));
+ return matrixKey | key;
}
private:
bool fSwapRedAndBlue;
GrConfigConversionEffect::PMConversion fPMConversion;
+ GrGLEffectMatrix fEffectMatrix;
- typedef GrGLProgramStage INHERITED;
+ typedef GrGLEffect INHERITED;
};
@@ -75,8 +100,9 @@ private:
GrConfigConversionEffect::GrConfigConversionEffect(GrTexture* texture,
bool swapRedAndBlue,
- PMConversion pmConversion)
- : GrSingleTextureEffect(texture)
+ PMConversion pmConversion,
+ const SkMatrix& matrix)
+ : GrSingleTextureEffect(texture, matrix)
, fSwapRedAndBlue(swapRedAndBlue)
, fPMConversion(pmConversion) {
GrAssert(kRGBA_8888_GrPixelConfig == texture->config() ||
@@ -85,22 +111,22 @@ GrConfigConversionEffect::GrConfigConversionEffect(GrTexture* texture,
GrAssert(swapRedAndBlue || kNone_PMConversion != pmConversion);
}
-const GrProgramStageFactory& GrConfigConversionEffect::getFactory() const {
- return GrTProgramStageFactory<GrConfigConversionEffect>::getInstance();
+const GrBackendEffectFactory& GrConfigConversionEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrConfigConversionEffect>::getInstance();
}
-bool GrConfigConversionEffect::isEqual(const GrCustomStage& s) const {
+bool GrConfigConversionEffect::isEqual(const GrEffect& s) const {
const GrConfigConversionEffect& other = static_cast<const GrConfigConversionEffect&>(s);
return other.fSwapRedAndBlue == fSwapRedAndBlue && other.fPMConversion == fPMConversion;
}
///////////////////////////////////////////////////////////////////////////////
-GR_DEFINE_CUSTOM_STAGE_TEST(GrConfigConversionEffect);
+GR_DEFINE_EFFECT_TEST(GrConfigConversionEffect);
-GrCustomStage* GrConfigConversionEffect::TestCreate(SkRandom* random,
- GrContext* context,
- GrTexture* textures[]) {
+GrEffect* GrConfigConversionEffect::TestCreate(SkRandom* random,
+ GrContext* context,
+ GrTexture* textures[]) {
PMConversion pmConv = static_cast<PMConversion>(random->nextULessThan(kPMConversionCnt));
bool swapRB;
if (kNone_PMConversion == pmConv) {
@@ -108,8 +134,10 @@ GrCustomStage* GrConfigConversionEffect::TestCreate(SkRandom* random,
} else {
swapRB = random->nextBool();
}
- return SkNEW_ARGS(GrConfigConversionEffect,
- (textures[GrCustomStageUnitTest::kSkiaPMTextureIdx], swapRB, pmConv));
+ return SkNEW_ARGS(GrConfigConversionEffect, (textures[GrEffectUnitTest::kSkiaPMTextureIdx],
+ swapRB,
+ pmConv,
+ GrEffectUnitTest::TestMatrix(random)));
}
///////////////////////////////////////////////////////////////////////////////
@@ -169,32 +197,40 @@ void GrConfigConversionEffect::TestForPreservingPMConversions(GrContext* context
*pmToUPMRule = kConversionRules[i][0];
*upmToPMRule = kConversionRules[i][1];
- static const GrRect kDstRect = GrRect::MakeWH(GrIntToScalar(256), GrIntToScalar(256));
- static const GrRect kSrcRect = GrRect::MakeWH(GR_Scalar1, GR_Scalar1);
+ static const GrRect kDstRect = GrRect::MakeWH(SkIntToScalar(256), SkIntToScalar(256));
+ static const GrRect kSrcRect = GrRect::MakeWH(SK_Scalar1, SK_Scalar1);
// We do a PM->UPM draw from dataTex to readTex and read the data. Then we do a UPM->PM draw
// from readTex to tempTex followed by a PM->UPM draw to readTex and finally read the data.
// We then verify that two reads produced the same values.
GrPaint paint;
-
- SkAutoTUnref<GrCustomStage> pmToUPMStage1(SkNEW_ARGS(GrConfigConversionEffect,
- (dataTex, false, *pmToUPMRule)));
- SkAutoTUnref<GrCustomStage> upmToPMStage(SkNEW_ARGS(GrConfigConversionEffect,
- (readTex, false, *upmToPMRule)));
- SkAutoTUnref<GrCustomStage> pmToUPMStage2(SkNEW_ARGS(GrConfigConversionEffect,
- (tempTex, false, *pmToUPMRule)));
+ SkAutoTUnref<GrEffect> pmToUPMEffect1(SkNEW_ARGS(GrConfigConversionEffect,
+ (dataTex,
+ false,
+ *pmToUPMRule,
+ SkMatrix::I())));
+ SkAutoTUnref<GrEffect> upmToPMEffect(SkNEW_ARGS(GrConfigConversionEffect,
+ (readTex,
+ false,
+ *upmToPMRule,
+ SkMatrix::I())));
+ SkAutoTUnref<GrEffect> pmToUPMEffect2(SkNEW_ARGS(GrConfigConversionEffect,
+ (tempTex,
+ false,
+ *pmToUPMRule,
+ SkMatrix::I())));
context->setRenderTarget(readTex->asRenderTarget());
- paint.colorSampler(0)->setCustomStage(pmToUPMStage1);
+ paint.colorStage(0)->setEffect(pmToUPMEffect1);
context->drawRectToRect(paint, kDstRect, kSrcRect);
readTex->readPixels(0, 0, 256, 256, kRGBA_8888_GrPixelConfig, firstRead);
context->setRenderTarget(tempTex->asRenderTarget());
- paint.colorSampler(0)->setCustomStage(upmToPMStage);
+ paint.colorStage(0)->setEffect(upmToPMEffect);
context->drawRectToRect(paint, kDstRect, kSrcRect);
context->setRenderTarget(readTex->asRenderTarget());
- paint.colorSampler(0)->setCustomStage(pmToUPMStage2);
+ paint.colorStage(0)->setEffect(pmToUPMEffect2);
context->drawRectToRect(paint, kDstRect, kSrcRect);
readTex->readPixels(0, 0, 256, 256, kRGBA_8888_GrPixelConfig, secondRead);
@@ -215,21 +251,27 @@ void GrConfigConversionEffect::TestForPreservingPMConversions(GrContext* context
}
}
-GrCustomStage* GrConfigConversionEffect::Create(GrTexture* texture,
- bool swapRedAndBlue,
- PMConversion pmConversion) {
+bool GrConfigConversionEffect::InstallEffect(GrTexture* texture,
+ bool swapRedAndBlue,
+ PMConversion pmConversion,
+ const SkMatrix& matrix,
+ GrEffectStage* stage) {
if (!swapRedAndBlue && kNone_PMConversion == pmConversion) {
// If we returned a GrConfigConversionEffect that was equivalent to a GrSingleTextureEffect
// then we may pollute our texture cache with redundant shaders. So in the case that no
// conversions were requested we instead return a GrSingleTextureEffect.
- return SkNEW_ARGS(GrSingleTextureEffect, (texture));
+ stage->setEffect(SkNEW_ARGS(GrSingleTextureEffect, (texture, matrix)))->unref();
+ return true;
} else {
if (kRGBA_8888_GrPixelConfig != texture->config() &&
kBGRA_8888_GrPixelConfig != texture->config() &&
kNone_PMConversion != pmConversion) {
// The PM conversions assume colors are 0..255
- return NULL;
+ return false;
}
- return SkNEW_ARGS(GrConfigConversionEffect, (texture, swapRedAndBlue, pmConversion));
+ stage->setEffect(SkNEW_ARGS(GrConfigConversionEffect, (texture,
+ swapRedAndBlue,
+ pmConversion, matrix)))->unref();
+ return true;
}
}
diff --git a/src/gpu/effects/GrConfigConversionEffect.h b/src/gpu/effects/GrConfigConversionEffect.h
index 9bb7e3a8c2..48c776ccf4 100644
--- a/src/gpu/effects/GrConfigConversionEffect.h
+++ b/src/gpu/effects/GrConfigConversionEffect.h
@@ -33,16 +33,18 @@ public:
kPMConversionCnt
};
- // This will fail if the config is not 8888 and a PM conversion is requested.
- static GrCustomStage* Create(GrTexture*,
- bool swapRedAndBlue,
- PMConversion pmConversion = kNone_PMConversion);
+ // Installs an effect in the GrEffectStage to perform a config conversion.
+ static bool InstallEffect(GrTexture*,
+ bool swapRedAndBlue,
+ PMConversion pmConversion,
+ const SkMatrix& matrix,
+ GrEffectStage* stage);
static const char* Name() { return "Config Conversion"; }
- typedef GrGLConfigConversionEffect GLProgramStage;
+ typedef GrGLConfigConversionEffect GLEffect;
- virtual const GrProgramStageFactory& getFactory() const SK_OVERRIDE;
- virtual bool isEqual(const GrCustomStage&) const SK_OVERRIDE;
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+ virtual bool isEqual(const GrEffect&) const SK_OVERRIDE;
bool swapsRedAndBlue() const { return fSwapRedAndBlue; }
PMConversion pmConversion() const { return fPMConversion; }
@@ -59,12 +61,13 @@ public:
private:
GrConfigConversionEffect(GrTexture*,
bool swapRedAndBlue,
- PMConversion pmConversion);
+ PMConversion pmConversion,
+ const SkMatrix& matrix);
bool fSwapRedAndBlue;
PMConversion fPMConversion;
- GR_DECLARE_CUSTOM_STAGE_TEST;
+ GR_DECLARE_EFFECT_TEST;
typedef GrSingleTextureEffect INHERITED;
};
diff --git a/src/gpu/effects/GrConvolutionEffect.cpp b/src/gpu/effects/GrConvolutionEffect.cpp
index c189ec0a2c..047dc4165e 100644
--- a/src/gpu/effects/GrConvolutionEffect.cpp
+++ b/src/gpu/effects/GrConvolutionEffect.cpp
@@ -6,66 +6,66 @@
*/
#include "GrConvolutionEffect.h"
-#include "gl/GrGLProgramStage.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
#include "gl/GrGLSL.h"
#include "gl/GrGLTexture.h"
-#include "GrProgramStageFactory.h"
+#include "GrTBackendEffectFactory.h"
// For brevity
typedef GrGLUniformManager::UniformHandle UniformHandle;
static const UniformHandle kInvalidUniformHandle = GrGLUniformManager::kInvalidUniformHandle;
-class GrGLConvolutionEffect : public GrGLProgramStage {
+class GrGLConvolutionEffect : public GrGLEffect {
public:
- GrGLConvolutionEffect(const GrProgramStageFactory& factory,
- const GrCustomStage& stage);
+ GrGLConvolutionEffect(const GrBackendEffectFactory&, const GrEffect&);
- virtual void setupVariables(GrGLShaderBuilder* builder) SK_OVERRIDE;
- virtual void emitVS(GrGLShaderBuilder* builder,
- const char* vertexCoords) SK_OVERRIDE {};
- virtual void emitFS(GrGLShaderBuilder* builder,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray&) SK_OVERRIDE;
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrEffectStage&,
+ EffectKey,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
- virtual void setData(const GrGLUniformManager& uman,
- const GrCustomStage&,
- const GrRenderTarget*,
- int stageNum) SK_OVERRIDE;
+ virtual void setData(const GrGLUniformManager& uman, const GrEffectStage&) SK_OVERRIDE;
- static inline StageKey GenKey(const GrCustomStage&, const GrGLCaps&);
+ static inline EffectKey GenKey(const GrEffectStage&, const GrGLCaps&);
private:
int width() const { return Gr1DKernelEffect::WidthFromRadius(fRadius); }
- int fRadius;
- UniformHandle fKernelUni;
- UniformHandle fImageIncrementUni;
+ int fRadius;
+ UniformHandle fKernelUni;
+ UniformHandle fImageIncrementUni;
+ GrGLEffectMatrix fEffectMatrix;
- typedef GrGLProgramStage INHERITED;
+ typedef GrGLEffect INHERITED;
};
-GrGLConvolutionEffect::GrGLConvolutionEffect(const GrProgramStageFactory& factory,
- const GrCustomStage& stage)
- : GrGLProgramStage(factory)
+GrGLConvolutionEffect::GrGLConvolutionEffect(const GrBackendEffectFactory& factory,
+ const GrEffect& effect)
+ : INHERITED(factory)
, fKernelUni(kInvalidUniformHandle)
, fImageIncrementUni(kInvalidUniformHandle) {
const GrConvolutionEffect& c =
- static_cast<const GrConvolutionEffect&>(stage);
+ static_cast<const GrConvolutionEffect&>(effect);
fRadius = c.radius();
}
-void GrGLConvolutionEffect::setupVariables(GrGLShaderBuilder* builder) {
+void GrGLConvolutionEffect::emitCode(GrGLShaderBuilder* builder,
+ const GrEffectStage&,
+ EffectKey key,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ const char* coords;
+ fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, vertexCoords, &coords);
fImageIncrementUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
kVec2f_GrSLType, "ImageIncrement");
fKernelUni = builder->addUniformArray(GrGLShaderBuilder::kFragment_ShaderType,
kFloat_GrSLType, "Kernel", this->width());
-}
-
-void GrGLConvolutionEffect::emitFS(GrGLShaderBuilder* builder,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray& samplers) {
SkString* code = &builder->fFSCode;
code->appendf("\t\t%s = vec4(0, 0, 0, 0);\n", outputColor);
@@ -74,8 +74,7 @@ void GrGLConvolutionEffect::emitFS(GrGLShaderBuilder* builder,
const GrGLShaderVar& kernel = builder->getUniformVariable(fKernelUni);
const char* imgInc = builder->getUniformCStr(fImageIncrementUni);
- code->appendf("\t\tvec2 coord = %s - %d.0 * %s;\n",
- builder->defaultTexCoordsName(), fRadius, imgInc);
+ code->appendf("\t\tvec2 coord = %s - %d.0 * %s;\n", coords, fRadius, imgInc);
// Manually unroll loop because some drivers don't; yields 20-30% speedup.
for (int i = 0; i < width; i++) {
@@ -91,13 +90,9 @@ void GrGLConvolutionEffect::emitFS(GrGLShaderBuilder* builder,
GrGLSLMulVarBy4f(&builder->fFSCode, 2, outputColor, inputColor);
}
-void GrGLConvolutionEffect::setData(const GrGLUniformManager& uman,
- const GrCustomStage& data,
- const GrRenderTarget*,
- int stageNum) {
- const GrConvolutionEffect& conv =
- static_cast<const GrConvolutionEffect&>(data);
- GrTexture& texture = *data.texture(0);
+void GrGLConvolutionEffect::setData(const GrGLUniformManager& uman, const GrEffectStage& stage) {
+ const GrConvolutionEffect& conv = static_cast<const GrConvolutionEffect&>(*stage.getEffect());
+ GrTexture& texture = *conv.texture(0);
// the code we generated was for a specific kernel radius
GrAssert(conv.radius() == fRadius);
float imageIncrement[2] = { 0 };
@@ -113,11 +108,17 @@ void GrGLConvolutionEffect::setData(const GrGLUniformManager& uman,
}
uman.set2fv(fImageIncrementUni, 0, 1, imageIncrement);
uman.set1fv(fKernelUni, 0, this->width(), conv.kernel());
+ fEffectMatrix.setData(uman, conv.getMatrix(), stage.getCoordChangeMatrix(), conv.texture(0));
}
-GrGLProgramStage::StageKey GrGLConvolutionEffect::GenKey(const GrCustomStage& s,
- const GrGLCaps& caps) {
- return static_cast<const GrConvolutionEffect&>(s).radius();
+GrGLEffect::EffectKey GrGLConvolutionEffect::GenKey(const GrEffectStage& s, const GrGLCaps&) {
+ const GrConvolutionEffect& conv = static_cast<const GrConvolutionEffect&>(*s.getEffect());
+ EffectKey key = static_cast<const GrConvolutionEffect&>(*s.getEffect()).radius();
+ key <<= GrGLEffectMatrix::kKeyBits;
+ EffectKey matrixKey = GrGLEffectMatrix::GenKey(conv.getMatrix(),
+ s.getCoordChangeMatrix(),
+ conv.texture(0));
+ return key | matrixKey;
}
///////////////////////////////////////////////////////////////////////////////
@@ -128,11 +129,10 @@ GrConvolutionEffect::GrConvolutionEffect(GrTexture* texture,
const float* kernel)
: Gr1DKernelEffect(texture, direction, radius) {
GrAssert(radius <= kMaxKernelRadius);
+ GrAssert(NULL != kernel);
int width = this->width();
- if (NULL != kernel) {
- for (int i = 0; i < width; i++) {
- fKernel[i] = kernel[i];
- }
+ for (int i = 0; i < width; i++) {
+ fKernel[i] = kernel[i];
}
}
@@ -163,11 +163,11 @@ GrConvolutionEffect::GrConvolutionEffect(GrTexture* texture,
GrConvolutionEffect::~GrConvolutionEffect() {
}
-const GrProgramStageFactory& GrConvolutionEffect::getFactory() const {
- return GrTProgramStageFactory<GrConvolutionEffect>::getInstance();
+const GrBackendEffectFactory& GrConvolutionEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrConvolutionEffect>::getInstance();
}
-bool GrConvolutionEffect::isEqual(const GrCustomStage& sBase) const {
+bool GrConvolutionEffect::isEqual(const GrEffect& sBase) const {
const GrConvolutionEffect& s =
static_cast<const GrConvolutionEffect&>(sBase);
return (INHERITED::isEqual(sBase) &&
@@ -178,13 +178,13 @@ bool GrConvolutionEffect::isEqual(const GrCustomStage& sBase) const {
///////////////////////////////////////////////////////////////////////////////
-GR_DEFINE_CUSTOM_STAGE_TEST(GrConvolutionEffect);
+GR_DEFINE_EFFECT_TEST(GrConvolutionEffect);
-GrCustomStage* GrConvolutionEffect::TestCreate(SkRandom* random,
- GrContext* context,
- GrTexture* textures[]) {
- int texIdx = random->nextBool() ? GrCustomStageUnitTest::kSkiaPMTextureIdx :
- GrCustomStageUnitTest::kAlphaTextureIdx;
+GrEffect* GrConvolutionEffect::TestCreate(SkRandom* random,
+ GrContext* context,
+ GrTexture* textures[]) {
+ int texIdx = random->nextBool() ? GrEffectUnitTest::kSkiaPMTextureIdx :
+ GrEffectUnitTest::kAlphaTextureIdx;
Direction dir = random->nextBool() ? kX_Direction : kY_Direction;
int radius = random->nextRangeU(1, kMaxKernelRadius);
float kernel[kMaxKernelRadius];
diff --git a/src/gpu/effects/GrConvolutionEffect.h b/src/gpu/effects/GrConvolutionEffect.h
index 4c42a6ce41..3638c0cf6a 100644
--- a/src/gpu/effects/GrConvolutionEffect.h
+++ b/src/gpu/effects/GrConvolutionEffect.h
@@ -23,21 +23,22 @@ public:
/// Convolve with an arbitrary user-specified kernel
GrConvolutionEffect(GrTexture*, Direction,
- int halfWidth, const float* kernel = NULL);
+ int halfWidth, const float* kernel);
- /// Convolve with a gaussian kernel
+ /// Convolve with a Gaussian kernel
GrConvolutionEffect(GrTexture*, Direction,
- int halfWidth, float gaussianSigma);
+ int halfWidth,
+ float gaussianSigma);
virtual ~GrConvolutionEffect();
const float* kernel() const { return fKernel; }
static const char* Name() { return "Convolution"; }
- typedef GrGLConvolutionEffect GLProgramStage;
+ typedef GrGLConvolutionEffect GLEffect;
- virtual const GrProgramStageFactory& getFactory() const SK_OVERRIDE;
- virtual bool isEqual(const GrCustomStage&) const SK_OVERRIDE;
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+ virtual bool isEqual(const GrEffect&) const SK_OVERRIDE;
enum {
// This was decided based on the min allowed value for the max texture
@@ -55,7 +56,7 @@ protected:
float fKernel[kMaxKernelWidth];
private:
- GR_DECLARE_CUSTOM_STAGE_TEST;
+ GR_DECLARE_EFFECT_TEST;
typedef Gr1DKernelEffect INHERITED;
};
diff --git a/src/gpu/effects/GrSingleTextureEffect.cpp b/src/gpu/effects/GrSingleTextureEffect.cpp
index f6f7b8c5bd..05eff6b3d4 100644
--- a/src/gpu/effects/GrSingleTextureEffect.cpp
+++ b/src/gpu/effects/GrSingleTextureEffect.cpp
@@ -6,33 +6,53 @@
*/
#include "effects/GrSingleTextureEffect.h"
-#include "gl/GrGLProgramStage.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
#include "gl/GrGLSL.h"
#include "gl/GrGLTexture.h"
-#include "GrProgramStageFactory.h"
+#include "GrTBackendEffectFactory.h"
#include "GrTexture.h"
-class GrGLSingleTextureEffect : public GrGLProgramStage {
+class GrGLSingleTextureEffect : public GrGLEffect {
public:
- GrGLSingleTextureEffect(const GrProgramStageFactory& factory,
- const GrCustomStage& stage) : INHERITED (factory) { }
-
- virtual void emitVS(GrGLShaderBuilder* builder,
- const char* vertexCoords) SK_OVERRIDE { }
- virtual void emitFS(GrGLShaderBuilder* builder,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray& samplers) SK_OVERRIDE {
+ GrGLSingleTextureEffect(const GrBackendEffectFactory& factory, const GrEffect&)
+ : INHERITED (factory) {}
+
+ virtual void emitCode(GrGLShaderBuilder* builder,
+ const GrEffectStage&,
+ EffectKey key,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) SK_OVERRIDE {
+ const char* coordName;
+ GrSLType coordType = fEffectMatrix.emitCode(builder, key, vertexCoords, &coordName);
builder->fFSCode.appendf("\t%s = ", outputColor);
- builder->appendTextureLookupAndModulate(&builder->fFSCode, inputColor, samplers[0]);
+ builder->appendTextureLookupAndModulate(&builder->fFSCode,
+ inputColor,
+ samplers[0],
+ coordName,
+ coordType);
builder->fFSCode.append(";\n");
}
- static inline StageKey GenKey(const GrCustomStage&, const GrGLCaps&) { return 0; }
+ static inline EffectKey GenKey(const GrEffectStage& stage, const GrGLCaps&) {
+ const GrSingleTextureEffect& ste =
+ static_cast<const GrSingleTextureEffect&>(*stage.getEffect());
+ return GrGLEffectMatrix::GenKey(ste.getMatrix(),
+ stage.getCoordChangeMatrix(),
+ ste.texture(0));
+ }
-private:
+ virtual void setData(const GrGLUniformManager& uman, const GrEffectStage& stage) SK_OVERRIDE {
+ const GrSingleTextureEffect& ste =
+ static_cast<const GrSingleTextureEffect&>(*stage.getEffect());
+ fEffectMatrix.setData(uman, ste.getMatrix(), stage.getCoordChangeMatrix(), ste.texture(0));
+ }
- typedef GrGLProgramStage INHERITED;
+private:
+ GrGLEffectMatrix fEffectMatrix;
+ typedef GrGLEffect INHERITED;
};
///////////////////////////////////////////////////////////////////////////////
@@ -40,16 +60,39 @@ private:
GrSingleTextureEffect::GrSingleTextureEffect(GrTexture* texture)
: INHERITED(1)
, fTextureAccess(texture) {
+ fMatrix.reset();
}
GrSingleTextureEffect::GrSingleTextureEffect(GrTexture* texture, bool bilerp)
: INHERITED(1)
, fTextureAccess(texture, bilerp) {
+ fMatrix.reset();
}
GrSingleTextureEffect::GrSingleTextureEffect(GrTexture* texture, const GrTextureParams& params)
: INHERITED(1)
, fTextureAccess(texture, params) {
+ fMatrix.reset();
+}
+
+GrSingleTextureEffect::GrSingleTextureEffect(GrTexture* texture, const SkMatrix& m)
+ : INHERITED(1)
+ , fTextureAccess(texture)
+ , fMatrix(m) {
+}
+
+GrSingleTextureEffect::GrSingleTextureEffect(GrTexture* texture, const SkMatrix& m, bool bilerp)
+ : INHERITED(1)
+ , fTextureAccess(texture, bilerp)
+ , fMatrix(m) {
+}
+
+GrSingleTextureEffect::GrSingleTextureEffect(GrTexture* texture,
+ const SkMatrix& m,
+ const GrTextureParams& params)
+ : INHERITED(1)
+ , fTextureAccess(texture, params)
+ , fMatrix(m) {
}
GrSingleTextureEffect::~GrSingleTextureEffect() {
@@ -60,18 +103,19 @@ const GrTextureAccess& GrSingleTextureEffect::textureAccess(int index) const {
return fTextureAccess;
}
-const GrProgramStageFactory& GrSingleTextureEffect::getFactory() const {
- return GrTProgramStageFactory<GrSingleTextureEffect>::getInstance();
+const GrBackendEffectFactory& GrSingleTextureEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrSingleTextureEffect>::getInstance();
}
///////////////////////////////////////////////////////////////////////////////
-GR_DEFINE_CUSTOM_STAGE_TEST(GrSingleTextureEffect);
+GR_DEFINE_EFFECT_TEST(GrSingleTextureEffect);
-GrCustomStage* GrSingleTextureEffect::TestCreate(SkRandom* random,
- GrContext* context,
- GrTexture* textures[]) {
- int texIdx = random->nextBool() ? GrCustomStageUnitTest::kSkiaPMTextureIdx :
- GrCustomStageUnitTest::kAlphaTextureIdx;
- return SkNEW_ARGS(GrSingleTextureEffect, (textures[texIdx]));
+GrEffect* GrSingleTextureEffect::TestCreate(SkRandom* random,
+ GrContext* context,
+ GrTexture* textures[]) {
+ int texIdx = random->nextBool() ? GrEffectUnitTest::kSkiaPMTextureIdx :
+ GrEffectUnitTest::kAlphaTextureIdx;
+ const SkMatrix& matrix = GrEffectUnitTest::TestMatrix(random);
+ return SkNEW_ARGS(GrSingleTextureEffect, (textures[texIdx], matrix));
}
diff --git a/src/gpu/effects/GrSingleTextureEffect.h b/src/gpu/effects/GrSingleTextureEffect.h
index ad47eb7ba7..709d3dcd2b 100644
--- a/src/gpu/effects/GrSingleTextureEffect.h
+++ b/src/gpu/effects/GrSingleTextureEffect.h
@@ -8,40 +8,60 @@
#ifndef GrSingleTextureEffect_DEFINED
#define GrSingleTextureEffect_DEFINED
-#include "GrCustomStage.h"
+#include "GrEffect.h"
+#include "SkMatrix.h"
+#include "GrTexture.h"
class GrGLSingleTextureEffect;
/**
- * An effect that merely blits a single texture; commonly used as a base class.
+ * An effect that draws a single texture with a texture matrix; commonly used as a base class. The
+ * output color is the texture color is modulated against the input color.
*/
-class GrSingleTextureEffect : public GrCustomStage {
+class GrSingleTextureEffect : public GrEffect {
public:
- /** Uses default texture params (unfiltered, clamp) */
- GrSingleTextureEffect(GrTexture* texture);
-
- /** Uses default tile mode (clamp) */
- GrSingleTextureEffect(GrTexture* texture, bool bilerp);
-
+ /** These three constructors assume an identity matrix. TODO: Remove these.*/
+ GrSingleTextureEffect(GrTexture* texture); /* unfiltered, clamp mode */
+ GrSingleTextureEffect(GrTexture* texture, bool bilerp); /* clamp mode */
GrSingleTextureEffect(GrTexture* texture, const GrTextureParams&);
+ /** These three constructors take an explicit matrix */
+ GrSingleTextureEffect(GrTexture*, const SkMatrix&); /* unfiltered, clamp mode */
+ GrSingleTextureEffect(GrTexture*, const SkMatrix&, bool bilerp); /* clamp mode */
+ GrSingleTextureEffect(GrTexture*, const SkMatrix&, const GrTextureParams&);
+
virtual ~GrSingleTextureEffect();
virtual const GrTextureAccess& textureAccess(int index) const SK_OVERRIDE;
static const char* Name() { return "Single Texture"; }
- typedef GrGLSingleTextureEffect GLProgramStage;
+ const SkMatrix& getMatrix() const { return fMatrix; }
+
+ typedef GrGLSingleTextureEffect GLEffect;
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+
+ virtual bool isEqual(const GrEffect& effect) const SK_OVERRIDE {
+ const GrSingleTextureEffect& ste = static_cast<const GrSingleTextureEffect&>(effect);
+ return INHERITED::isEqual(effect) && fMatrix.cheapEqualTo(ste.getMatrix());
+ }
- virtual const GrProgramStageFactory& getFactory() const SK_OVERRIDE;
+ static inline SkMatrix MakeDivByTextureWHMatrix(const GrTexture* texture) {
+ GrAssert(NULL != texture);
+ SkMatrix mat;
+ mat.setIDiv(texture->width(), texture->height());
+ return mat;
+ }
private:
- GR_DECLARE_CUSTOM_STAGE_TEST;
+ GR_DECLARE_EFFECT_TEST;
GrTextureAccess fTextureAccess;
+ SkMatrix fMatrix;
- typedef GrCustomStage INHERITED;
+ typedef GrEffect INHERITED;
};
#endif
diff --git a/src/gpu/effects/GrTextureDomainEffect.cpp b/src/gpu/effects/GrTextureDomainEffect.cpp
index c00f40f08f..6884682829 100644
--- a/src/gpu/effects/GrTextureDomainEffect.cpp
+++ b/src/gpu/effects/GrTextureDomainEffect.cpp
@@ -6,100 +6,154 @@
*/
#include "GrTextureDomainEffect.h"
-#include "gl/GrGLProgramStage.h"
-#include "GrProgramStageFactory.h"
+#include "GrTBackendEffectFactory.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
+#include "SkFloatingPoint.h"
-class GrGLTextureDomainEffect : public GrGLProgramStage {
+class GrGLTextureDomainEffect : public GrGLEffect {
public:
- GrGLTextureDomainEffect(const GrProgramStageFactory& factory,
- const GrCustomStage& stage);
+ GrGLTextureDomainEffect(const GrBackendEffectFactory&, const GrEffect&);
- virtual void setupVariables(GrGLShaderBuilder* builder) SK_OVERRIDE;
- virtual void emitVS(GrGLShaderBuilder* builder,
- const char* vertexCoords) SK_OVERRIDE { }
- virtual void emitFS(GrGLShaderBuilder* builder,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray&) SK_OVERRIDE;
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrEffectStage&,
+ EffectKey,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
- virtual void setData(const GrGLUniformManager&,
- const GrCustomStage&,
- const GrRenderTarget*,
- int stageNum) SK_OVERRIDE;
+ virtual void setData(const GrGLUniformManager&, const GrEffectStage&) SK_OVERRIDE;
- static inline StageKey GenKey(const GrCustomStage&, const GrGLCaps&) { return 0; }
+ static inline EffectKey GenKey(const GrEffectStage&, const GrGLCaps&);
private:
GrGLUniformManager::UniformHandle fNameUni;
+ GrGLEffectMatrix fEffectMatrix;
+ GrGLfloat fPrevDomain[4];
- typedef GrGLProgramStage INHERITED;
+ typedef GrGLEffect INHERITED;
};
-GrGLTextureDomainEffect::GrGLTextureDomainEffect(const GrProgramStageFactory& factory,
- const GrCustomStage& stage)
- : GrGLProgramStage(factory)
+GrGLTextureDomainEffect::GrGLTextureDomainEffect(const GrBackendEffectFactory& factory,
+ const GrEffect&)
+ : INHERITED(factory)
, fNameUni(GrGLUniformManager::kInvalidUniformHandle) {
+ fPrevDomain[0] = SK_FloatNaN;
}
-void GrGLTextureDomainEffect::setupVariables(GrGLShaderBuilder* builder) {
+void GrGLTextureDomainEffect::emitCode(GrGLShaderBuilder* builder,
+ const GrEffectStage& stage,
+ EffectKey key,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ const GrTextureDomainEffect& effect =
+ static_cast<const GrTextureDomainEffect&>(*stage.getEffect());
+
+ const char* coords;
+ fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, vertexCoords, &coords);
+ const char* domain;
fNameUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
- kVec4f_GrSLType, "TexDom");
-};
-
-void GrGLTextureDomainEffect::emitFS(GrGLShaderBuilder* builder,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray& samplers) {
- builder->fFSCode.appendf("\tvec2 clampCoord = clamp(%s, %s.xy, %s.zw);\n",
- builder->defaultTexCoordsName(),
- builder->getUniformCStr(fNameUni),
- builder->getUniformCStr(fNameUni));
-
- builder->fFSCode.appendf("\t%s = ", outputColor);
- builder->appendTextureLookupAndModulate(&builder->fFSCode,
- inputColor,
- samplers[0],
- "clampCoord");
- builder->fFSCode.append(";\n");
+ kVec4f_GrSLType, "TexDom", &domain);
+ if (GrTextureDomainEffect::kClamp_WrapMode == effect.wrapMode()) {
+
+ builder->fFSCode.appendf("\tvec2 clampCoord = clamp(%s, %s.xy, %s.zw);\n",
+ coords, domain, domain);
+
+ builder->fFSCode.appendf("\t%s = ", outputColor);
+ builder->appendTextureLookupAndModulate(&builder->fFSCode,
+ inputColor,
+ samplers[0],
+ "clampCoord");
+ builder->fFSCode.append(";\n");
+ } else {
+ GrAssert(GrTextureDomainEffect::kDecal_WrapMode == effect.wrapMode());
+ builder->fFSCode.append("\tbvec4 outside;\n");
+ builder->fFSCode.appendf("\toutside.xy = lessThan(%s, %s.xy);\n", coords, domain);
+ builder->fFSCode.appendf("\toutside.zw = greaterThan(%s, %s.zw);\n", coords, domain);
+ builder->fFSCode.appendf("\t%s = any(outside) ? vec4(0.0, 0.0, 0.0, 0.0) : ", outputColor);
+ builder->appendTextureLookupAndModulate(&builder->fFSCode, inputColor, samplers[0], coords);
+ builder->fFSCode.append(";\n");
+ }
}
-void GrGLTextureDomainEffect::setData(const GrGLUniformManager& uman,
- const GrCustomStage& data,
- const GrRenderTarget*,
- int stageNum) {
- const GrTextureDomainEffect& effect = static_cast<const GrTextureDomainEffect&>(data);
+void GrGLTextureDomainEffect::setData(const GrGLUniformManager& uman, const GrEffectStage& stage) {
+ const GrTextureDomainEffect& effect =
+ static_cast<const GrTextureDomainEffect&>(*stage.getEffect());
const GrRect& domain = effect.domain();
float values[4] = {
- GrScalarToFloat(domain.left()),
- GrScalarToFloat(domain.top()),
- GrScalarToFloat(domain.right()),
- GrScalarToFloat(domain.bottom())
+ SkScalarToFloat(domain.left()),
+ SkScalarToFloat(domain.top()),
+ SkScalarToFloat(domain.right()),
+ SkScalarToFloat(domain.bottom())
};
// vertical flip if necessary
- const GrGLTexture* texture = static_cast<const GrGLTexture*>(effect.texture(0));
- if (GrGLTexture::kBottomUp_Orientation == texture->orientation()) {
+ if (GrSurface::kBottomLeft_Origin == effect.texture(0)->origin()) {
values[1] = 1.0f - values[1];
values[3] = 1.0f - values[3];
// The top and bottom were just flipped, so correct the ordering
// of elements so that values = (l, t, r, b).
SkTSwap(values[1], values[3]);
}
- uman.set4fv(fNameUni, 0, 1, values);
+ if (0 != memcmp(values, fPrevDomain, 4 * sizeof(GrGLfloat))) {
+ uman.set4fv(fNameUni, 0, 1, values);
+ }
+ fEffectMatrix.setData(uman,
+ effect.getMatrix(),
+ stage.getCoordChangeMatrix(),
+ effect.texture(0));
+}
+
+GrGLEffect::EffectKey GrGLTextureDomainEffect::GenKey(const GrEffectStage& stage, const GrGLCaps&) {
+ const GrTextureDomainEffect& effect =
+ static_cast<const GrTextureDomainEffect&>(*stage.getEffect());
+ EffectKey key = effect.wrapMode();
+ key <<= GrGLEffectMatrix::kKeyBits;
+ EffectKey matrixKey = GrGLEffectMatrix::GenKey(effect.getMatrix(),
+ stage.getCoordChangeMatrix(),
+ effect.texture(0));
+ return key | matrixKey;
}
///////////////////////////////////////////////////////////////////////////////
-GrTextureDomainEffect::GrTextureDomainEffect(GrTexture* texture, const GrRect& domain)
- : GrSingleTextureEffect(texture)
- , fTextureDomain(domain) {
+GrEffect* GrTextureDomainEffect::Create(GrTexture* texture,
+ const SkMatrix& matrix,
+ const GrRect& domain,
+ WrapMode wrapMode,
+ bool bilerp) {
+ static const SkRect kFullRect = {0, 0, SK_Scalar1, SK_Scalar1};
+ if (kClamp_WrapMode == wrapMode && domain.contains(kFullRect)) {
+ return SkNEW_ARGS(GrSingleTextureEffect, (texture, matrix, bilerp));
+ } else {
+ SkRect clippedDomain;
+ // We don't currently handle domains that are empty or don't intersect the texture.
+ // It is OK if the domain rect is a line or point, but it should not be inverted. We do not
+ // handle rects that do not intersect the [0..1]x[0..1] rect.
+ GrAssert(domain.fLeft <= domain.fRight);
+ GrAssert(domain.fTop <= domain.fBottom);
+ clippedDomain.fLeft = SkMaxScalar(domain.fLeft, kFullRect.fLeft);
+ clippedDomain.fRight = SkMinScalar(domain.fRight, kFullRect.fRight);
+ clippedDomain.fTop = SkMaxScalar(domain.fTop, kFullRect.fTop);
+ clippedDomain.fBottom = SkMinScalar(domain.fBottom, kFullRect.fBottom);
+ GrAssert(clippedDomain.fLeft <= clippedDomain.fRight);
+ GrAssert(clippedDomain.fTop <= clippedDomain.fBottom);
+ return SkNEW_ARGS(GrTextureDomainEffect,
+ (texture, matrix, clippedDomain, wrapMode, bilerp));
+ }
}
GrTextureDomainEffect::GrTextureDomainEffect(GrTexture* texture,
+ const SkMatrix& matrix,
const GrRect& domain,
- const GrTextureParams& params)
- : GrSingleTextureEffect(texture, params)
+ WrapMode wrapMode,
+ bool bilerp)
+ : GrSingleTextureEffect(texture, matrix, bilerp)
+ , fWrapMode(wrapMode)
, fTextureDomain(domain) {
}
@@ -107,28 +161,30 @@ GrTextureDomainEffect::~GrTextureDomainEffect() {
}
-const GrProgramStageFactory& GrTextureDomainEffect::getFactory() const {
- return GrTProgramStageFactory<GrTextureDomainEffect>::getInstance();
+const GrBackendEffectFactory& GrTextureDomainEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrTextureDomainEffect>::getInstance();
}
-bool GrTextureDomainEffect::isEqual(const GrCustomStage& sBase) const {
+bool GrTextureDomainEffect::isEqual(const GrEffect& sBase) const {
const GrTextureDomainEffect& s = static_cast<const GrTextureDomainEffect&>(sBase);
return (INHERITED::isEqual(sBase) && this->fTextureDomain == s.fTextureDomain);
}
///////////////////////////////////////////////////////////////////////////////
-GR_DEFINE_CUSTOM_STAGE_TEST(GrTextureDomainEffect);
+GR_DEFINE_EFFECT_TEST(GrTextureDomainEffect);
-GrCustomStage* GrTextureDomainEffect::TestCreate(SkRandom* random,
- GrContext* context,
- GrTexture* textures[]) {
- int texIdx = random->nextBool() ? GrCustomStageUnitTest::kSkiaPMTextureIdx :
- GrCustomStageUnitTest::kAlphaTextureIdx;
+GrEffect* GrTextureDomainEffect::TestCreate(SkRandom* random,
+ GrContext* context,
+ GrTexture* textures[]) {
+ int texIdx = random->nextBool() ? GrEffectUnitTest::kSkiaPMTextureIdx :
+ GrEffectUnitTest::kAlphaTextureIdx;
GrRect domain;
domain.fLeft = random->nextUScalar1();
domain.fRight = random->nextRangeScalar(domain.fLeft, SK_Scalar1);
domain.fTop = random->nextUScalar1();
domain.fBottom = random->nextRangeScalar(domain.fTop, SK_Scalar1);
- return SkNEW_ARGS(GrTextureDomainEffect, (textures[texIdx], domain));
+ WrapMode wrapMode = random->nextBool() ? kClamp_WrapMode : kDecal_WrapMode;
+ const SkMatrix& matrix = GrEffectUnitTest::TestMatrix(random);
+ return GrTextureDomainEffect::Create(textures[texIdx], matrix, domain, wrapMode);
}
diff --git a/src/gpu/effects/GrTextureDomainEffect.h b/src/gpu/effects/GrTextureDomainEffect.h
index 872d57de2d..c1ce6d11a6 100644
--- a/src/gpu/effects/GrTextureDomainEffect.h
+++ b/src/gpu/effects/GrTextureDomainEffect.h
@@ -8,40 +8,76 @@
#ifndef GrTextureDomainEffect_DEFINED
#define GrTextureDomainEffect_DEFINED
-//#include "GrCustomStage.h"
#include "GrSingleTextureEffect.h"
#include "GrRect.h"
class GrGLTextureDomainEffect;
/**
- * Limits a texture's lookup coordinates to a domain.
+ * Limits a texture's lookup coordinates to a domain. Samples outside the domain are either clamped
+ * the edge of the domain or result in a vec4 of zeros. The domain is clipped to normalized texture
+ * coords ([0,1]x[0,1] square). Bilinear filtering can cause texels outside the domain to affect the
+ * read value unless the caller considers this when calculating the domain. TODO: This should be a
+ * helper that can assist an effect rather than effect unto itself.
*/
class GrTextureDomainEffect : public GrSingleTextureEffect {
public:
- /** Uses default texture params (no filter, clamp) */
- GrTextureDomainEffect(GrTexture*, const GrRect& domain);
-
- GrTextureDomainEffect(GrTexture*, const GrRect& domain, const GrTextureParams& params);
+ /**
+ * If SkShader::kDecal_TileMode sticks then this enum could be replaced by SkShader::TileMode.
+ * We could also consider replacing/augmenting Decal mode with Border mode where the color
+ * outside of the domain is user-specifiable. Decal mode currently has a hard (non-lerped)
+ * transition between the border and the interior.
+ */
+ enum WrapMode {
+ kClamp_WrapMode,
+ kDecal_WrapMode,
+ };
+
+ static GrEffect* Create(GrTexture*,
+ const SkMatrix&,
+ const SkRect& domain,
+ WrapMode,
+ bool bilerp = false);
virtual ~GrTextureDomainEffect();
static const char* Name() { return "TextureDomain"; }
- typedef GrGLTextureDomainEffect GLProgramStage;
+ typedef GrGLTextureDomainEffect GLEffect;
- virtual const GrProgramStageFactory& getFactory() const SK_OVERRIDE;
- virtual bool isEqual(const GrCustomStage&) const SK_OVERRIDE;
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+ virtual bool isEqual(const GrEffect&) const SK_OVERRIDE;
- const GrRect& domain() const { return fTextureDomain; }
+ const SkRect& domain() const { return fTextureDomain; }
+ WrapMode wrapMode() const { return fWrapMode; }
-protected:
+ /* Computes a domain that bounds all the texels in texelRect. Note that with bilerp enabled
+ texels neighboring the domain may be read. */
+ static const SkRect MakeTexelDomain(const GrTexture* texture, const SkIRect& texelRect) {
+ SkScalar wInv = SK_Scalar1 / texture->width();
+ SkScalar hInv = SK_Scalar1 / texture->height();
+ SkRect result = {
+ texelRect.fLeft * wInv,
+ texelRect.fTop * hInv,
+ texelRect.fRight * wInv,
+ texelRect.fBottom * hInv
+ };
+ return result;
+ }
- GrRect fTextureDomain;
+protected:
+ WrapMode fWrapMode;
+ SkRect fTextureDomain;
private:
- GR_DECLARE_CUSTOM_STAGE_TEST;
+ GrTextureDomainEffect(GrTexture*,
+ const SkMatrix&,
+ const GrRect& domain,
+ WrapMode,
+ bool bilerp);
+
+ GR_DECLARE_EFFECT_TEST;
typedef GrSingleTextureEffect INHERITED;
};
diff --git a/src/gpu/effects/GrTextureStripAtlas.h b/src/gpu/effects/GrTextureStripAtlas.h
index a6833e0201..210d88ec90 100644
--- a/src/gpu/effects/GrTextureStripAtlas.h
+++ b/src/gpu/effects/GrTextureStripAtlas.h
@@ -66,8 +66,8 @@ public:
* atlas and scaleFactor, returned by getVerticalScaleFactor(), is the y-scale of the row,
* relative to the height of the overall atlas texture.
*/
- GrScalar getYOffset(int row) const { return SkIntToScalar(row) / fNumRows; }
- GrScalar getVerticalScaleFactor() const { return SkIntToScalar(fDesc.fRowHeight) / fDesc.fHeight; }
+ SkScalar getYOffset(int row) const { return SkIntToScalar(row) / fNumRows; }
+ SkScalar getVerticalScaleFactor() const { return SkIntToScalar(fDesc.fRowHeight) / fDesc.fHeight; }
GrContext* getContext() const { return fDesc.fContext; }
GrTexture* getTexture() const { return fTexture; }
diff --git a/src/gpu/gl/GrGLCaps.cpp b/src/gpu/gl/GrGLCaps.cpp
index 52c480ad3f..ec80cda760 100644
--- a/src/gpu/gl/GrGLCaps.cpp
+++ b/src/gpu/gl/GrGLCaps.cpp
@@ -36,6 +36,7 @@ void GrGLCaps::reset() {
fTextureRedSupport = false;
fImagingSupport = false;
fTwoFormatLimit = false;
+ fFragCoordsConventionSupport = false;
}
GrGLCaps::GrGLCaps(const GrGLCaps& caps) {
@@ -65,6 +66,7 @@ GrGLCaps& GrGLCaps::operator = (const GrGLCaps& caps) {
fTextureRedSupport = caps.fTextureRedSupport;
fImagingSupport = caps.fImagingSupport;
fTwoFormatLimit = caps.fTwoFormatLimit;
+ fFragCoordsConventionSupport = caps.fFragCoordsConventionSupport;
return *this;
}
@@ -158,6 +160,13 @@ void GrGLCaps::init(const GrGLContextInfo& ctxInfo) {
// can change based on which render target is bound
fTwoFormatLimit = kES2_GrGLBinding == binding;
+ // Known issue on at least some Intel platforms:
+ // http://code.google.com/p/skia/issues/detail?id=946
+ if (kIntel_GrGLVendor != ctxInfo.vendor()) {
+ fFragCoordsConventionSupport = ctxInfo.glslGeneration() >= k150_GrGLSLGeneration ||
+ ctxInfo.hasExtension("GL_ARB_fragment_coord_conventions");
+ }
+
this->initFSAASupport(ctxInfo);
this->initStencilFormats(ctxInfo);
}
@@ -415,5 +424,6 @@ void GrGLCaps::print() const {
GrPrintf("Pack Flip Y support: %s\n",
(fPackFlipYSupport ? "YES": "NO"));
GrPrintf("Two Format Limit: %s\n", (fTwoFormatLimit ? "YES": "NO"));
+ GrPrintf("Fragment coord conventions support: %s\n", (fFragCoordsConventionSupport ? "YES": "NO"));
}
diff --git a/src/gpu/gl/GrGLCaps.h b/src/gpu/gl/GrGLCaps.h
index 1cc50c2220..9dfbf23cb4 100644
--- a/src/gpu/gl/GrGLCaps.h
+++ b/src/gpu/gl/GrGLCaps.h
@@ -216,6 +216,9 @@ public:
/// Is GL_ARB_IMAGING supported
bool imagingSupport() const { return fImagingSupport; }
+ /// Is GL_ARB_fragment_coord_conventions supported?
+ bool fragCoordConventionsSupport() const { return fFragCoordsConventionSupport; }
+
// Does ReadPixels support the provided format/type combo?
bool readPixelsSupported(const GrGLInterface* intf,
GrGLenum format,
@@ -293,6 +296,7 @@ private:
bool fTextureRedSupport : 1;
bool fImagingSupport : 1;
bool fTwoFormatLimit : 1;
+ bool fFragCoordsConventionSupport : 1;
};
#endif
diff --git a/src/gpu/gl/GrGLContextInfo.cpp b/src/gpu/gl/GrGLContextInfo.cpp
index 9802e65985..060d236b22 100644
--- a/src/gpu/gl/GrGLContextInfo.cpp
+++ b/src/gpu/gl/GrGLContextInfo.cpp
@@ -30,6 +30,7 @@ GrGLContextInfo& GrGLContextInfo::operator = (const GrGLContextInfo& ctx) {
fBindingInUse = ctx.fBindingInUse;
fGLVersion = ctx.fGLVersion;
fGLSLGeneration = ctx.fGLSLGeneration;
+ fVendor = ctx.fVendor;
fExtensionString = ctx.fExtensionString;
fGLCaps = ctx.fGLCaps;
return *this;
@@ -40,6 +41,7 @@ void GrGLContextInfo::reset() {
fBindingInUse = kNone_GrGLBinding;
fGLVersion = GR_GL_VER(0, 0);
fGLSLGeneration = static_cast<GrGLSLGeneration>(0);
+ fVendor = kOther_GrGLVendor;
fExtensionString = "";
fGLCaps.reset();
}
@@ -70,7 +72,7 @@ bool GrGLContextInfo::initialize(const GrGLInterface* interface) {
const GrGLubyte* ext;
GR_GL_CALL_RET(interface, ext, GetString(GR_GL_EXTENSIONS));
fExtensionString = reinterpret_cast<const char*>(ext);
-
+ fVendor = GrGLGetVendor(interface);
fGLCaps.init(*this);
return true;
}
diff --git a/src/gpu/gl/GrGLContextInfo.h b/src/gpu/gl/GrGLContextInfo.h
index 44fc98555f..a6c997f5a2 100644
--- a/src/gpu/gl/GrGLContextInfo.h
+++ b/src/gpu/gl/GrGLContextInfo.h
@@ -31,7 +31,7 @@ public:
/**
* Creates a GrGLContextInfo from a GrGLInterface and the currently
- * bound OpenGL context accesible by the GrGLInterface.
+ * bound OpenGL context accessible by the GrGLInterface.
*/
explicit GrGLContextInfo(const GrGLInterface* interface);
@@ -58,6 +58,7 @@ public:
GrGLBinding binding() const { return fBindingInUse; }
GrGLVersion version() const { return fGLVersion; }
GrGLSLGeneration glslGeneration() const { return fGLSLGeneration; }
+ GrGLVendor vendor() const { return fVendor; }
const GrGLCaps& caps() const { return fGLCaps; }
GrGLCaps& caps() { return fGLCaps; }
@@ -79,6 +80,7 @@ private:
GrGLBinding fBindingInUse;
GrGLVersion fGLVersion;
GrGLSLGeneration fGLSLGeneration;
+ GrGLVendor fVendor;
SkString fExtensionString;
GrGLCaps fGLCaps;
};
diff --git a/src/gpu/gl/GrGLCreateNullInterface.cpp b/src/gpu/gl/GrGLCreateNullInterface.cpp
index d0e2bb6d3f..6cca6c89f6 100644
--- a/src/gpu/gl/GrGLCreateNullInterface.cpp
+++ b/src/gpu/gl/GrGLCreateNullInterface.cpp
@@ -49,7 +49,11 @@ GrGLvoid GR_GL_FUNCTION_TYPE nullGLQueryCounter(GrGLuint id, GrGLenum target) {}
GrGLvoid GR_GL_FUNCTION_TYPE nullGLReadBuffer(GrGLenum src) {}
GrGLvoid GR_GL_FUNCTION_TYPE nullGLReadPixels(GrGLint x, GrGLint y, GrGLsizei width, GrGLsizei height, GrGLenum format, GrGLenum type, GrGLvoid* pixels) {}
GrGLvoid GR_GL_FUNCTION_TYPE nullGLScissor(GrGLint x, GrGLint y, GrGLsizei width, GrGLsizei height) {}
+#if GR_USE_NEW_GL_SHADER_SOURCE_SIGNATURE
+GrGLvoid GR_GL_FUNCTION_TYPE nullGLShaderSource(GrGLuint shader, GrGLsizei count, const char* const * str, const GrGLint* length) {}
+#else
GrGLvoid GR_GL_FUNCTION_TYPE nullGLShaderSource(GrGLuint shader, GrGLsizei count, const char** str, const GrGLint* length) {}
+#endif
GrGLvoid GR_GL_FUNCTION_TYPE nullGLStencilFunc(GrGLenum func, GrGLint ref, GrGLuint mask) {}
GrGLvoid GR_GL_FUNCTION_TYPE nullGLStencilFuncSeparate(GrGLenum face, GrGLenum func, GrGLint ref, GrGLuint mask) {}
GrGLvoid GR_GL_FUNCTION_TYPE nullGLStencilMask(GrGLuint mask) {}
diff --git a/src/gpu/gl/GrGLEffect.cpp b/src/gpu/gl/GrGLEffect.cpp
new file mode 100644
index 0000000000..0bbf1f7ccb
--- /dev/null
+++ b/src/gpu/gl/GrGLEffect.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrGLSL.h"
+#include "GrGLEffect.h"
+
+GrGLEffect::GrGLEffect(const GrBackendEffectFactory& factory)
+ : fFactory(factory) {
+}
+
+GrGLEffect::~GrGLEffect() {
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void GrGLEffect::setData(const GrGLUniformManager&, const GrEffectStage&) {
+}
+
+GrGLEffect::EffectKey GrGLEffect::GenTextureKey(const GrEffect& effect,
+ const GrGLCaps& caps) {
+ EffectKey key = 0;
+ for (int index = 0; index < effect.numTextures(); ++index) {
+ const GrTextureAccess& access = effect.textureAccess(index);
+ EffectKey value = GrGLShaderBuilder::KeyForTextureAccess(access, caps) << index;
+ GrAssert(0 == (value & key)); // keys for each access ought not to overlap
+ key |= value;
+ }
+ return key;
+}
diff --git a/src/gpu/gl/GrGLEffect.h b/src/gpu/gl/GrGLEffect.h
new file mode 100644
index 0000000000..f2142c5b22
--- /dev/null
+++ b/src/gpu/gl/GrGLEffect.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrGLEffect_DEFINED
+#define GrGLEffect_DEFINED
+
+#include "GrBackendEffectFactory.h"
+#include "GrGLShaderBuilder.h"
+#include "GrGLShaderVar.h"
+#include "GrGLSL.h"
+
+class GrEffectStage;
+class GrGLTexture;
+
+/** @file
+ This file contains specializations for OpenGL of the shader stages declared in
+ include/gpu/GrEffect.h. Objects of type GrGLEffect are responsible for emitting the
+ GLSL code that implements a GrEffect and for uploading uniforms at draw time. They also
+ must have a function:
+ static inline EffectKey GenKey(const GrEffectStage&, const GrGLCaps&)
+ that is used to implement a program cache. When two GrEffects produce the same key this means
+ that their GrGLEffects would emit the same GLSL code.
+
+ These objects are created by the factory object returned by the GrEffect::getFactory().
+*/
+
+class GrGLEffect {
+
+public:
+ typedef GrBackendEffectFactory::EffectKey EffectKey;
+
+ enum {
+ kNoEffectKey = GrBackendEffectFactory::kNoEffectKey,
+ // the number of bits in EffectKey available to GenKey
+ kEffectKeyBits = GrBackendEffectFactory::kEffectKeyBits,
+ };
+
+ typedef GrGLShaderBuilder::TextureSamplerArray TextureSamplerArray;
+
+ GrGLEffect(const GrBackendEffectFactory&);
+
+ virtual ~GrGLEffect();
+
+ /** Called when the program stage should insert its code into the shaders. The code in each
+ shader will be in its own block ({}) and so locally scoped names will not collide across
+ stages.
+
+ @param builder Interface used to emit code in the shaders.
+ @param stage The effect stage that generated this program stage.
+ @param key The key that was computed by GenKey() from the generating GrEffect.
+ Only the bits indicated by GrBackendEffectFactory::kEffectKeyBits are
+ guaranteed to match the value produced by GenKey();
+ @param vertexCoords A vec2 in the VS that holds the position in local coords. This is either
+ the pre-view-matrix vertex position or if explicit per-vertex texture
+ coords are used with a stage then it is those coordinates. See
+ GrVertexLayout.
+ @param outputColor A predefined vec4 in the FS in which the stage should place its output
+ color (or coverage).
+ @param inputColor A vec4 that holds the input color to the stage in the FS. This may be
+ NULL in which case the implied input is solid white (all ones).
+ TODO: Better system for communicating optimization info (e.g. input
+ color is solid white, trans black, known to be opaque, etc.) that allows
+ the effect to communicate back similar known info about its output.
+ @param samplers One entry for each GrTextureAccess of the GrEffect that generated the
+ GrGLEffect. These can be passed to the builder to emit texture
+ reads in the generated code.
+ */
+ virtual void emitCode(GrGLShaderBuilder* builder,
+ const GrEffectStage& stage,
+ EffectKey key,
+ const char* vertexCoords,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) = 0;
+
+ /** A GrGLEffect instance can be reused with any GrEffect that produces the same stage
+ key; this function reads data from a stage and uploads any uniform variables required
+ by the shaders created in emitCode(). The GrEffect installed in the GrEffectStage is
+ guaranteed to be of the same type that created this GrGLEffect and to have an identical
+ EffectKey as the one that created this GrGLEffect. */
+ virtual void setData(const GrGLUniformManager&, const GrEffectStage&);
+
+ const char* name() const { return fFactory.name(); }
+
+ static EffectKey GenTextureKey(const GrEffect&, const GrGLCaps&);
+
+protected:
+ const GrBackendEffectFactory& fFactory;
+};
+
+#endif
diff --git a/src/gpu/gl/GrGLEffectMatrix.cpp b/src/gpu/gl/GrGLEffectMatrix.cpp
new file mode 100644
index 0000000000..6dcb6e6096
--- /dev/null
+++ b/src/gpu/gl/GrGLEffectMatrix.cpp
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrGLEffectMatrix.h"
+#include "GrTexture.h"
+
+GrGLEffect::EffectKey GrGLEffectMatrix::GenKey(const SkMatrix& effectMatrix,
+ const SkMatrix& coordChangeMatrix,
+ const GrTexture* texture) {
+ SkMatrix::TypeMask type0 = effectMatrix.getType();
+ SkMatrix::TypeMask type1 = coordChangeMatrix.getType();
+
+ static const int kNonTransMask = SkMatrix::kAffine_Mask |
+ SkMatrix::kScale_Mask |
+ SkMatrix::kPerspective_Mask;
+ int combinedTypes = type0 | type1;
+
+ bool reverseY = (NULL != texture) && GrSurface::kBottomLeft_Origin == texture->origin();
+
+ if (SkMatrix::kPerspective_Mask & combinedTypes) {
+ return kGeneral_Key;
+ } else if ((kNonTransMask & combinedTypes) || reverseY) {
+ return kNoPersp_Key;
+ } else if (kTrans_Key & combinedTypes) {
+ return kTrans_Key;
+ } else {
+ GrAssert(effectMatrix.isIdentity() && coordChangeMatrix.isIdentity());
+ return kIdentity_Key;
+ }
+}
+
+GrSLType GrGLEffectMatrix::emitCode(GrGLShaderBuilder* builder,
+ EffectKey key,
+ const char* vertexCoords,
+ const char** fsCoordName,
+ const char** vsCoordName,
+ const char* suffix) {
+ GrSLType varyingType;
+ const char* uniName;
+ key &= kKeyMask;
+ switch (key) {
+ case kIdentity_Key:
+ fUniType = kVoid_GrSLType;
+ varyingType = kVec2f_GrSLType;
+ break;
+ case kTrans_Key:
+ fUniType = kVec2f_GrSLType;
+ uniName = "StageTranslate";
+ varyingType = kVec2f_GrSLType;
+ break;
+ case kNoPersp_Key:
+ fUniType = kMat33f_GrSLType;
+ uniName = "StageMatrix";
+ varyingType = kVec2f_GrSLType;
+ break;
+ case kGeneral_Key:
+ fUniType = kMat33f_GrSLType;
+ uniName = "StageMatrix";
+ varyingType = kVec3f_GrSLType;
+ break;
+ default:
+ GrCrash("Unexpected key.");
+ }
+ SkString suffixedUniName;
+ if (NULL != suffix) {
+ suffixedUniName.append(uniName);
+ suffixedUniName.append(suffix);
+ uniName = suffixedUniName.c_str();
+ }
+ if (kVoid_GrSLType != fUniType) {
+ fUni = builder->addUniform(GrGLShaderBuilder::kVertex_ShaderType,
+ fUniType,
+ uniName,
+ &uniName);
+ }
+
+ const char* varyingName = "StageCoord";
+ SkString suffixedVaryingName;
+ if (NULL != suffix) {
+ suffixedVaryingName.append(varyingName);
+ suffixedVaryingName.append(suffix);
+ varyingName = suffixedVaryingName.c_str();
+ }
+ const char* vsVaryingName;
+ const char* fsVaryingName;
+ builder->addVarying(varyingType, varyingName, &vsVaryingName, &fsVaryingName);
+
+ // varying = matrix * vertex-coords (logically)
+ switch (fUniType) {
+ case kVoid_GrSLType:
+ GrAssert(kVec2f_GrSLType == varyingType);
+ builder->fVSCode.appendf("\t%s = %s;\n", vsVaryingName, vertexCoords);
+ break;
+ case kVec2f_GrSLType:
+ GrAssert(kVec2f_GrSLType == varyingType);
+ builder->fVSCode.appendf("\t%s = %s + %s;\n", vsVaryingName, uniName, vertexCoords);
+ break;
+ case kMat33f_GrSLType: {
+ GrAssert(kVec2f_GrSLType == varyingType || kVec3f_GrSLType == varyingType);
+ if (kVec2f_GrSLType == varyingType) {
+ builder->fVSCode.appendf("\t%s = (%s * vec3(%s, 1)).xy;\n",
+ vsVaryingName, uniName, vertexCoords);
+ } else {
+ builder->fVSCode.appendf("\t%s = %s * vec3(%s, 1);\n",
+ vsVaryingName, uniName, vertexCoords);
+ }
+ break;
+ }
+ default:
+ GrCrash("Unexpected uniform type.");
+ }
+ if (NULL != vsCoordName) {
+ *vsCoordName = vsVaryingName;
+ }
+ if (NULL != fsCoordName) {
+ *fsCoordName = fsVaryingName;
+ }
+ return varyingType;
+}
+
+/**
+ * This is similar to emitCode except that it performs perspective division in the FS if the
+ * texture coordinates have a w coordinate. The fsCoordName always refers to a vec2f.
+ */
+void GrGLEffectMatrix::emitCodeMakeFSCoords2D(GrGLShaderBuilder* builder,
+ EffectKey key,
+ const char* vertexCoords,
+ const char** fsCoordName,
+ const char** vsVaryingName,
+ GrSLType* vsVaryingType,
+ const char* suffix) {
+ const char* fsVaryingName;
+
+ GrSLType varyingType = this->emitCode(builder,
+ key,
+ vertexCoords,
+ &fsVaryingName,
+ vsVaryingName,
+ suffix);
+ if (kVec3f_GrSLType == varyingType) {
+
+ const char* coordName = "coords2D";
+ SkString suffixedCoordName;
+ if (NULL != suffix) {
+ suffixedCoordName.append(coordName);
+ suffixedCoordName.append(suffix);
+ coordName = suffixedCoordName.c_str();
+ }
+ builder->fFSCode.appendf("\tvec2 %s = %s.xy / %s.z;",
+ coordName, fsVaryingName, fsVaryingName);
+ if (NULL != fsCoordName) {
+ *fsCoordName = coordName;
+ }
+ } else if(NULL != fsCoordName) {
+ *fsCoordName = fsVaryingName;
+ }
+ if (NULL != vsVaryingType) {
+ *vsVaryingType = varyingType;
+ }
+}
+
+void GrGLEffectMatrix::setData(const GrGLUniformManager& uniformManager,
+ const SkMatrix& matrix,
+ const SkMatrix& coordChangeMatrix,
+ const GrTexture* texture) {
+ GrAssert((GrGLUniformManager::kInvalidUniformHandle == fUni) ==
+ (kVoid_GrSLType == fUniType));
+ switch (fUniType) {
+ case kVoid_GrSLType:
+ GrAssert(matrix.isIdentity());
+ GrAssert(coordChangeMatrix.isIdentity());
+ GrAssert(NULL == texture || GrSurface::kTopLeft_Origin == texture->origin());
+ return;
+ case kVec2f_GrSLType: {
+ GrAssert(SkMatrix::kTranslate_Mask == (matrix.getType() | coordChangeMatrix.getType()));
+ GrAssert(NULL == texture || GrSurface::kTopLeft_Origin == texture->origin());
+ SkScalar tx = matrix[SkMatrix::kMTransX] + coordChangeMatrix[SkMatrix::kMTransX];
+ SkScalar ty = matrix[SkMatrix::kMTransY] + coordChangeMatrix[SkMatrix::kMTransY];
+ if (fPrevMatrix.get(SkMatrix::kMTransX) != tx ||
+ fPrevMatrix.get(SkMatrix::kMTransY) != ty) {
+ uniformManager.set2f(fUni, tx, ty);
+ fPrevMatrix.set(SkMatrix::kMTransX, tx);
+ fPrevMatrix.set(SkMatrix::kMTransY, ty);
+ }
+ break;
+ }
+ case kMat33f_GrSLType: {
+ SkMatrix combined;
+ combined.setConcat(matrix, coordChangeMatrix);
+ if (NULL != texture && GrSurface::kBottomLeft_Origin == texture->origin()) {
+ // combined.postScale(1,-1);
+ // combined.postTranslate(0,1);
+ combined.set(SkMatrix::kMSkewY,
+ combined[SkMatrix::kMPersp0] - combined[SkMatrix::kMSkewY]);
+ combined.set(SkMatrix::kMScaleY,
+ combined[SkMatrix::kMPersp1] - combined[SkMatrix::kMScaleY]);
+ combined.set(SkMatrix::kMTransY,
+ combined[SkMatrix::kMPersp2] - combined[SkMatrix::kMTransY]);
+ }
+ if (!fPrevMatrix.cheapEqualTo(combined)) {
+ uniformManager.setSkMatrix(fUni, combined);
+ fPrevMatrix = combined;
+ }
+ break;
+ }
+ default:
+ GrCrash("Unexpected uniform type.");
+ }
+}
diff --git a/src/gpu/gl/GrGLEffectMatrix.h b/src/gpu/gl/GrGLEffectMatrix.h
new file mode 100644
index 0000000000..49ddceb129
--- /dev/null
+++ b/src/gpu/gl/GrGLEffectMatrix.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrGLEffectMatrix_DEFINED
+#define GrGLEffectMatrix_DEFINED
+
+#include "GrGLEffect.h"
+#include "SkMatrix.h"
+
+class GrTexture;
+class SkRandom;
+
+/**
+ * This is a helper to implement a texture matrix in a GrGLEffect.
+ */
+class GrGLEffectMatrix {
+public:
+ typedef GrGLEffect::EffectKey EffectKey;
+ /**
+ * The matrix uses kKeyBits of the effect's EffectKey. A GrGLEffect may place these bits at an
+ * arbitrary shift in its final key. However, when GrGLEffectMatrix::emitCode*() code is called
+ * the relevant bits must be in the lower kKeyBits of the key parameter.
+ */
+ enum {
+ kKeyBits = 2,
+ kKeyMask = (1 << kKeyBits) - 1,
+ };
+
+ GrGLEffectMatrix() : fUni(GrGLUniformManager::kInvalidUniformHandle) {
+ fPrevMatrix = SkMatrix::InvalidMatrix();
+ }
+
+ /**
+ * Generates the key for the portion of the code emitted by this class's emitCode() function.
+ * Pass a texture to make GrGLEffectMatrix automatically adjust for the texture's origin. Pass
+ * NULL when not using the EffectMatrix for a texture lookups, or if the GrGLEffect subclass
+ * wants to handle origin adjustments in some other manner. coordChangeMatrix is the matrix
+ * from GrEffectStage.
+ */
+ static EffectKey GenKey(const SkMatrix& effectMatrix,
+ const SkMatrix& coordChangeMatrix,
+ const GrTexture*);
+
+ /**
+ * Emits code to implement the matrix in the VS. A varying is added as an output of the VS and
+ * input to the FS. The varying may be either a vec2f or vec3f depending upon whether
+ * perspective interpolation is required or not. The names of the varying in the VS and FS are
+ * are returned as output parameters and the type of the varying is the return value. The suffix
+ * is an optional parameter that can be used to make all variables emitted by the object
+ * unique within a stage. It is only necessary if multiple GrGLEffectMatrix objects are used by
+ * a GrGLEffect.
+ */
+ GrSLType emitCode(GrGLShaderBuilder*,
+ EffectKey,
+ const char* vertexCoords,
+ const char** fsCoordName, /* optional */
+ const char** vsCoordName = NULL,
+ const char* suffix = NULL);
+
+ /**
+ * This is similar to emitCode except that it performs perspective division in the FS if the
+ * texture coordinates have a w coordinate. The fsCoordName always refers to a vec2f.
+ */
+ void emitCodeMakeFSCoords2D(GrGLShaderBuilder*,
+ EffectKey,
+ const char* vertexCoords,
+ const char** fsCoordName, /* optional */
+ const char** vsVaryingName = NULL,
+ GrSLType* vsVaryingType = NULL,
+ const char* suffix = NULL);
+ /**
+ * Call from a GrGLEffect's subclass to update the texture matrix. The matrix,
+ * coordChangeMatrix, and texture params should match those used with GenKey.
+ */
+ void setData(const GrGLUniformManager& uniformManager,
+ const SkMatrix& effectMatrix,
+ const SkMatrix& coordChangeMatrix,
+ const GrTexture*);
+
+private:
+ enum {
+ kIdentity_Key = 0,
+ kTrans_Key = 1,
+ kNoPersp_Key = 2,
+ kGeneral_Key = 3,
+ };
+
+ GrGLUniformManager::UniformHandle fUni;
+ GrSLType fUniType;
+ SkMatrix fPrevMatrix;
+};
+
+#endif
diff --git a/src/gpu/gl/GrGLProgram.cpp b/src/gpu/gl/GrGLProgram.cpp
index 2703110df6..0558701322 100644
--- a/src/gpu/gl/GrGLProgram.cpp
+++ b/src/gpu/gl/GrGLProgram.cpp
@@ -8,11 +8,10 @@
#include "GrGLProgram.h"
#include "GrAllocator.h"
-#include "GrCustomStage.h"
-#include "GrGLProgramStage.h"
-#include "gl/GrGLShaderBuilder.h"
+#include "GrEffect.h"
+#include "GrGLEffect.h"
#include "GrGLShaderVar.h"
-#include "GrProgramStageFactory.h"
+#include "GrBackendEffectFactory.h"
#include "SkTrace.h"
#include "SkXfermode.h"
@@ -23,9 +22,6 @@ SK_DEFINE_INST_COUNT(GrGLProgram)
#define PRINT_SHADERS 0
-typedef GrGLProgram::Desc::StageDesc StageDesc;
-
-#define POS_ATTR_NAME "aPosition"
#define COL_ATTR_NAME "aColor"
#define COV_ATTR_NAME "aCoverage"
#define EDGE_ATTR_NAME "aEdge"
@@ -53,8 +49,8 @@ inline const char* dual_source_output_name() { return "dualSourceOut"; }
GrGLProgram* GrGLProgram::Create(const GrGLContextInfo& gl,
const Desc& desc,
- const GrCustomStage** customStages) {
- GrGLProgram* program = SkNEW_ARGS(GrGLProgram, (gl, desc, customStages));
+ const GrEffectStage* stages[]) {
+ GrGLProgram* program = SkNEW_ARGS(GrGLProgram, (gl, desc, stages));
if (!program->succeeded()) {
delete program;
program = NULL;
@@ -64,7 +60,7 @@ GrGLProgram* GrGLProgram::Create(const GrGLContextInfo& gl,
GrGLProgram::GrGLProgram(const GrGLContextInfo& gl,
const Desc& desc,
- const GrCustomStage** customStages)
+ const GrEffectStage* stages[])
: fContextInfo(gl)
, fUniformManager(gl) {
fDesc = desc;
@@ -73,19 +69,17 @@ GrGLProgram::GrGLProgram(const GrGLContextInfo& gl,
fFShaderID = 0;
fProgramID = 0;
- fViewMatrix = GrMatrix::InvalidMatrix();
+ fViewMatrix = SkMatrix::InvalidMatrix();
fViewportSize.set(-1, -1);
fColor = GrColor_ILLEGAL;
fColorFilterColor = GrColor_ILLEGAL;
+ fRTHeight = -1;
for (int s = 0; s < GrDrawState::kNumStages; ++s) {
- fProgramStage[s] = NULL;
- fTextureMatrices[s] = GrMatrix::InvalidMatrix();
- // this is arbitrary, just initialize to something
- fTextureOrientation[s] = GrGLTexture::kBottomUp_Orientation;
+ fEffects[s] = NULL;
}
- this->genProgram(customStages);
+ this->genProgram(stages);
}
GrGLProgram::~GrGLProgram() {
@@ -103,7 +97,7 @@ GrGLProgram::~GrGLProgram() {
}
for (int i = 0; i < GrDrawState::kNumStages; ++i) {
- delete fProgramStage[i];
+ delete fEffects[i];
}
}
@@ -234,61 +228,65 @@ static void addColorFilter(SkString* fsCode, const char * outputVar,
}
bool GrGLProgram::genEdgeCoverage(SkString* coverageVar,
- GrGLShaderBuilder* segments) const {
+ GrGLShaderBuilder* builder) const {
if (fDesc.fVertexLayout & GrDrawTarget::kEdge_VertexLayoutBit) {
const char *vsName, *fsName;
- segments->addVarying(kVec4f_GrSLType, "Edge", &vsName, &fsName);
- segments->fVSAttrs.push_back().set(kVec4f_GrSLType,
- GrGLShaderVar::kAttribute_TypeModifier, EDGE_ATTR_NAME);
- segments->fVSCode.appendf("\t%s = " EDGE_ATTR_NAME ";\n", vsName);
+ builder->addVarying(kVec4f_GrSLType, "Edge", &vsName, &fsName);
+ builder->fVSAttrs.push_back().set(kVec4f_GrSLType,
+ GrGLShaderVar::kAttribute_TypeModifier,
+ EDGE_ATTR_NAME);
+ builder->fVSCode.appendf("\t%s = " EDGE_ATTR_NAME ";\n", vsName);
switch (fDesc.fVertexEdgeType) {
case GrDrawState::kHairLine_EdgeType:
- segments->fFSCode.appendf("\tfloat edgeAlpha = abs(dot(vec3(gl_FragCoord.xy,1), %s.xyz));\n", fsName);
- segments->fFSCode.append("\tedgeAlpha = max(1.0 - edgeAlpha, 0.0);\n");
+ builder->fFSCode.appendf("\tfloat edgeAlpha = abs(dot(vec3(%s.xy,1), %s.xyz));\n", builder->fragmentPosition(), fsName);
+ builder->fFSCode.append("\tedgeAlpha = max(1.0 - edgeAlpha, 0.0);\n");
break;
case GrDrawState::kQuad_EdgeType:
- segments->fFSCode.append("\tfloat edgeAlpha;\n");
+ builder->fFSCode.append("\tfloat edgeAlpha;\n");
// keep the derivative instructions outside the conditional
- segments->fFSCode.appendf("\tvec2 duvdx = dFdx(%s.xy);\n", fsName);
- segments->fFSCode.appendf("\tvec2 duvdy = dFdy(%s.xy);\n", fsName);
- segments->fFSCode.appendf("\tif (%s.z > 0.0 && %s.w > 0.0) {\n", fsName, fsName);
+ builder->fFSCode.appendf("\tvec2 duvdx = dFdx(%s.xy);\n", fsName);
+ builder->fFSCode.appendf("\tvec2 duvdy = dFdy(%s.xy);\n", fsName);
+ builder->fFSCode.appendf("\tif (%s.z > 0.0 && %s.w > 0.0) {\n", fsName, fsName);
// today we know z and w are in device space. We could use derivatives
- segments->fFSCode.appendf("\t\tedgeAlpha = min(min(%s.z, %s.w) + 0.5, 1.0);\n", fsName, fsName);
- segments->fFSCode.append ("\t} else {\n");
- segments->fFSCode.appendf("\t\tvec2 gF = vec2(2.0*%s.x*duvdx.x - duvdx.y,\n"
- "\t\t 2.0*%s.x*duvdy.x - duvdy.y);\n",
- fsName, fsName);
- segments->fFSCode.appendf("\t\tedgeAlpha = (%s.x*%s.x - %s.y);\n", fsName, fsName, fsName);
- segments->fFSCode.append("\t\tedgeAlpha = clamp(0.5 - edgeAlpha / length(gF), 0.0, 1.0);\n"
- "\t}\n");
+ builder->fFSCode.appendf("\t\tedgeAlpha = min(min(%s.z, %s.w) + 0.5, 1.0);\n", fsName, fsName);
+ builder->fFSCode.append ("\t} else {\n");
+ builder->fFSCode.appendf("\t\tvec2 gF = vec2(2.0*%s.x*duvdx.x - duvdx.y,\n"
+ "\t\t 2.0*%s.x*duvdy.x - duvdy.y);\n",
+ fsName, fsName);
+ builder->fFSCode.appendf("\t\tedgeAlpha = (%s.x*%s.x - %s.y);\n", fsName, fsName, fsName);
+ builder->fFSCode.append("\t\tedgeAlpha = clamp(0.5 - edgeAlpha / length(gF), 0.0, 1.0);\n"
+ "\t}\n");
if (kES2_GrGLBinding == fContextInfo.binding()) {
- segments->fHeader.printf("#extension GL_OES_standard_derivatives: enable\n");
+ builder->fHeader.printf("#extension GL_OES_standard_derivatives: enable\n");
}
break;
case GrDrawState::kHairQuad_EdgeType:
- segments->fFSCode.appendf("\tvec2 duvdx = dFdx(%s.xy);\n", fsName);
- segments->fFSCode.appendf("\tvec2 duvdy = dFdy(%s.xy);\n", fsName);
- segments->fFSCode.appendf("\tvec2 gF = vec2(2.0*%s.x*duvdx.x - duvdx.y,\n"
- "\t 2.0*%s.x*duvdy.x - duvdy.y);\n",
- fsName, fsName);
- segments->fFSCode.appendf("\tfloat edgeAlpha = (%s.x*%s.x - %s.y);\n", fsName, fsName, fsName);
- segments->fFSCode.append("\tedgeAlpha = sqrt(edgeAlpha*edgeAlpha / dot(gF, gF));\n");
- segments->fFSCode.append("\tedgeAlpha = max(1.0 - edgeAlpha, 0.0);\n");
+ builder->fFSCode.appendf("\tvec2 duvdx = dFdx(%s.xy);\n", fsName);
+ builder->fFSCode.appendf("\tvec2 duvdy = dFdy(%s.xy);\n", fsName);
+ builder->fFSCode.appendf("\tvec2 gF = vec2(2.0*%s.x*duvdx.x - duvdx.y,\n"
+ "\t 2.0*%s.x*duvdy.x - duvdy.y);\n",
+ fsName, fsName);
+ builder->fFSCode.appendf("\tfloat edgeAlpha = (%s.x*%s.x - %s.y);\n", fsName, fsName, fsName);
+ builder->fFSCode.append("\tedgeAlpha = sqrt(edgeAlpha*edgeAlpha / dot(gF, gF));\n");
+ builder->fFSCode.append("\tedgeAlpha = max(1.0 - edgeAlpha, 0.0);\n");
if (kES2_GrGLBinding == fContextInfo.binding()) {
- segments->fHeader.printf("#extension GL_OES_standard_derivatives: enable\n");
+ builder->fHeader.printf("#extension GL_OES_standard_derivatives: enable\n");
}
break;
case GrDrawState::kCircle_EdgeType:
- segments->fFSCode.append("\tfloat edgeAlpha;\n");
- segments->fFSCode.appendf("\tfloat d = distance(gl_FragCoord.xy, %s.xy);\n", fsName);
- segments->fFSCode.appendf("\tfloat outerAlpha = smoothstep(d - 0.5, d + 0.5, %s.z);\n", fsName);
- segments->fFSCode.appendf("\tfloat innerAlpha = %s.w == 0.0 ? 1.0 : smoothstep(%s.w - 0.5, %s.w + 0.5, d);\n", fsName, fsName, fsName);
- segments->fFSCode.append("\tedgeAlpha = outerAlpha * innerAlpha;\n");
+ builder->fFSCode.append("\tfloat edgeAlpha;\n");
+ builder->fFSCode.appendf("\tfloat d = distance(%s.xy, %s.xy);\n", builder->fragmentPosition(), fsName);
+ builder->fFSCode.appendf("\tfloat outerAlpha = smoothstep(d - 0.5, d + 0.5, %s.z);\n", fsName);
+ builder->fFSCode.appendf("\tfloat innerAlpha = %s.w == 0.0 ? 1.0 : smoothstep(%s.w - 0.5, %s.w + 0.5, d);\n", fsName, fsName, fsName);
+ builder->fFSCode.append("\tedgeAlpha = outerAlpha * innerAlpha;\n");
break;
default:
GrCrash("Unknown Edge Type!");
break;
}
+ if (fDesc.fDiscardIfOutsideEdge) {
+ builder->fFSCode.appendf("\tif (edgeAlpha <= 0.0) {\n\t\tdiscard;\n\t}\n");
+ }
*coverageVar = "edgeAlpha";
return true;
} else {
@@ -364,9 +362,8 @@ void GrGLProgram::genGeometryShader(GrGLShaderBuilder* segments) const {
GrAssert(fContextInfo.glslGeneration() >= k150_GrGLSLGeneration);
segments->fGSHeader.append("layout(triangles) in;\n"
"layout(triangle_strip, max_vertices = 6) out;\n");
- segments->fGSCode.append("void main() {\n"
- "\tfor (int i = 0; i < 3; ++i) {\n"
- "\t\tgl_Position = gl_in[i].gl_Position;\n");
+ segments->fGSCode.append("\tfor (int i = 0; i < 3; ++i) {\n"
+ "\t\tgl_Position = gl_in[i].gl_Position;\n");
if (fDesc.fEmitsPointSize) {
segments->fGSCode.append("\t\tgl_PointSize = 1.0;\n");
}
@@ -379,8 +376,7 @@ void GrGLProgram::genGeometryShader(GrGLShaderBuilder* segments) const {
}
segments->fGSCode.append("\t\tEmitVertex();\n"
"\t}\n"
- "\tEndPrimitive();\n"
- "}\n");
+ "\tEndPrimitive();\n");
}
#endif
}
@@ -500,7 +496,7 @@ bool GrGLProgram::compileShaders(const GrGLShaderBuilder& builder) {
return true;
}
-bool GrGLProgram::genProgram(const GrCustomStage** customStages) {
+bool GrGLProgram::genProgram(const GrEffectStage* stages[]) {
GrAssert(0 == fProgramID);
GrGLShaderBuilder builder(fContextInfo, fUniformManager);
@@ -511,7 +507,6 @@ bool GrGLProgram::genProgram(const GrCustomStage** customStages) {
#endif
SkXfermode::Coeff colorCoeff, uniformCoeff;
- bool applyColorMatrix = SkToBool(fDesc.fColorMatrixEnabled);
// The rest of transfer mode color filters have not been implemented
if (fDesc.fColorFilterXfermode < SkXfermode::kCoeffModesCnt) {
GR_DEBUGCODE(bool success =)
@@ -524,17 +519,15 @@ bool GrGLProgram::genProgram(const GrCustomStage** customStages) {
uniformCoeff = SkXfermode::kZero_Coeff;
}
- // no need to do the color filter / matrix at all if coverage is 0. The
- // output color is scaled by the coverage. All the dual source outputs are
- // scaled by the coverage as well.
+ // no need to do the color filter if coverage is 0. The output color is scaled by the coverage.
+ // All the dual source outputs are scaled by the coverage as well.
if (Desc::kTransBlack_ColorInput == fDesc.fCoverageInput) {
colorCoeff = SkXfermode::kZero_Coeff;
uniformCoeff = SkXfermode::kZero_Coeff;
- applyColorMatrix = false;
}
// If we know the final color is going to be all zeros then we can
- // simplify the color filter coeffecients. needComputedColor will then
+ // simplify the color filter coefficients. needComputedColor will then
// come out false below.
if (Desc::kTransBlack_ColorInput == fDesc.fColorInput) {
colorCoeff = SkXfermode::kZero_Coeff;
@@ -570,14 +563,10 @@ bool GrGLProgram::genProgram(const GrCustomStage** customStages) {
fUniforms.fViewMatrixUni = builder.addUniform(GrGLShaderBuilder::kVertex_ShaderType,
kMat33f_GrSLType, "ViewM", &viewMName);
- builder.fVSAttrs.push_back().set(kVec2f_GrSLType,
- GrGLShaderVar::kAttribute_TypeModifier,
- POS_ATTR_NAME);
- builder.fVSCode.appendf("void main() {\n"
- "\tvec3 pos3 = %s * vec3("POS_ATTR_NAME", 1);\n"
- "\tgl_Position = vec4(pos3.xy, 0, pos3.z);\n",
- viewMName);
+ builder.fVSCode.appendf("\tvec3 pos3 = %s * vec3(%s, 1);\n"
+ "\tgl_Position = vec4(pos3.xy, 0, pos3.z);\n",
+ viewMName, builder.positionAttribute().getName().c_str());
// incoming color to current stage being processed.
SkString inColor;
@@ -591,8 +580,6 @@ bool GrGLProgram::genProgram(const GrCustomStage** customStages) {
builder.fVSCode.append("\tgl_PointSize = 1.0;\n");
}
- builder.fFSCode.append("void main() {\n");
-
// add texture coordinates that are used to the list of vertex attr decls
SkString texCoordAttrs[GrDrawState::kMaxTexCoords];
for (int t = 0; t < GrDrawState::kMaxTexCoords; ++t) {
@@ -612,7 +599,7 @@ bool GrGLProgram::genProgram(const GrCustomStage** customStages) {
if (needComputedColor) {
SkString outColor;
for (int s = 0; s < fDesc.fFirstCoverageStage; ++s) {
- if (fDesc.fStages[s].isEnabled()) {
+ if (GrGLEffect::kNoEffectKey != fDesc.fEffectKeys[s]) {
// create var to hold stage result
outColor = "color";
outColor.appendS32(s);
@@ -622,7 +609,7 @@ bool GrGLProgram::genProgram(const GrCustomStage** customStages) {
// figure out what our input coords are
int tcIdx = GrDrawTarget::VertexTexCoordsForStage(s, layout);
if (tcIdx < 0) {
- inCoords = POS_ATTR_NAME;
+ inCoords = builder.positionAttribute().c_str();
} else {
// must have input tex coordinates if stage is enabled.
GrAssert(texCoordAttrs[tcIdx].size());
@@ -630,13 +617,13 @@ bool GrGLProgram::genProgram(const GrCustomStage** customStages) {
}
builder.setCurrentStage(s);
- fProgramStage[s] = GenStageCode(customStages[s],
- fDesc.fStages[s],
- &fUniforms.fStages[s],
- inColor.size() ? inColor.c_str() : NULL,
- outColor.c_str(),
- inCoords,
- &builder);
+ fEffects[s] = GenStageCode(*stages[s],
+ fDesc.fEffectKeys[s],
+ &fUniforms.fStages[s],
+ inColor.size() ? inColor.c_str() : NULL,
+ outColor.c_str(),
+ inCoords,
+ &builder);
builder.setNonStage();
inColor = outColor;
}
@@ -664,8 +651,7 @@ bool GrGLProgram::genProgram(const GrCustomStage** customStages) {
}
bool wroteFragColorZero = false;
if (SkXfermode::kZero_Coeff == uniformCoeff &&
- SkXfermode::kZero_Coeff == colorCoeff &&
- !applyColorMatrix) {
+ SkXfermode::kZero_Coeff == colorCoeff) {
builder.fFSCode.appendf("\t%s = %s;\n",
colorOutput.getName().c_str(),
GrGLSLZerosVecf(4));
@@ -677,22 +663,6 @@ bool GrGLProgram::genProgram(const GrCustomStage** customStages) {
colorCoeff, colorFilterColorUniName, color);
inColor = "filteredColor";
}
- if (applyColorMatrix) {
- const char* colMatrixName;
- const char* colMatrixVecName;
- fUniforms.fColorMatrixUni = builder.addUniform(GrGLShaderBuilder::kFragment_ShaderType,
- kMat44f_GrSLType, "ColorMatrix",
- &colMatrixName);
- fUniforms.fColorMatrixVecUni = builder.addUniform(GrGLShaderBuilder::kFragment_ShaderType,
- kVec4f_GrSLType, "ColorMatrixVec",
- &colMatrixVecName);
- const char* color = adjustInColor(inColor);
- builder.fFSCode.appendf("\tvec4 matrixedColor = %s * vec4(%s.rgb / %s.a, %s.a) + %s;\n",
- colMatrixName, color, color, color, colMatrixVecName);
- builder.fFSCode.append("\tmatrixedColor.rgb *= matrixedColor.a;\n");
-
- inColor = "matrixedColor";
- }
///////////////////////////////////////////////////////////////////////////
// compute the partial coverage (coverage stages and edge aa)
@@ -725,7 +695,7 @@ bool GrGLProgram::genProgram(const GrCustomStage** customStages) {
SkString outCoverage;
const int& startStage = fDesc.fFirstCoverageStage;
for (int s = startStage; s < GrDrawState::kNumStages; ++s) {
- if (fDesc.fStages[s].isEnabled()) {
+ if (fDesc.fEffectKeys[s]) {
// create var to hold stage output
outCoverage = "coverage";
outCoverage.appendS32(s);
@@ -736,7 +706,7 @@ bool GrGLProgram::genProgram(const GrCustomStage** customStages) {
int tcIdx =
GrDrawTarget::VertexTexCoordsForStage(s, layout);
if (tcIdx < 0) {
- inCoords = POS_ATTR_NAME;
+ inCoords = builder.positionAttribute().c_str();
} else {
// must have input tex coordinates if stage is
// enabled.
@@ -752,13 +722,13 @@ bool GrGLProgram::genProgram(const GrCustomStage** customStages) {
inCoverage.append("4");
}
builder.setCurrentStage(s);
- fProgramStage[s] = GenStageCode(customStages[s],
- fDesc.fStages[s],
- &fUniforms.fStages[s],
- inCoverage.size() ? inCoverage.c_str() : NULL,
- outCoverage.c_str(),
- inCoords,
- &builder);
+ fEffects[s] = GenStageCode(*stages[s],
+ fDesc.fEffectKeys[s],
+ &fUniforms.fStages[s],
+ inCoverage.size() ? inCoverage.c_str() : NULL,
+ outCoverage.c_str(),
+ inCoords,
+ &builder);
builder.setNonStage();
inCoverage = outCoverage;
}
@@ -811,9 +781,6 @@ bool GrGLProgram::genProgram(const GrCustomStage** customStages) {
}
}
- builder.fVSCode.append("}\n");
- builder.fFSCode.append("}\n");
-
///////////////////////////////////////////////////////////////////////////
// insert GS
#if GR_DEBUG
@@ -827,7 +794,8 @@ bool GrGLProgram::genProgram(const GrCustomStage** customStages) {
return false;
}
- if (!this->bindOutputsAttribsAndLinkProgram(texCoordAttrs,
+ if (!this->bindOutputsAttribsAndLinkProgram(builder,
+ texCoordAttrs,
isColorDeclared,
dualSourceOutputWritten)) {
return false;
@@ -835,11 +803,13 @@ bool GrGLProgram::genProgram(const GrCustomStage** customStages) {
builder.finished(fProgramID);
this->initSamplerUniforms();
+ fUniforms.fRTHeight = builder.getRTHeightUniform();
return true;
}
-bool GrGLProgram::bindOutputsAttribsAndLinkProgram(SkString texCoordAttrNames[],
+bool GrGLProgram::bindOutputsAttribsAndLinkProgram(const GrGLShaderBuilder& builder,
+ SkString texCoordAttrNames[],
bool bindColorOut,
bool bindDualSrcOut) {
GL_CALL_RET(fProgramID, CreateProgram());
@@ -861,7 +831,9 @@ bool GrGLProgram::bindOutputsAttribsAndLinkProgram(SkString texCoordAttrNames[],
}
// Bind the attrib locations to same values for all shaders
- GL_CALL(BindAttribLocation(fProgramID, PositionAttributeIdx(), POS_ATTR_NAME));
+ GL_CALL(BindAttribLocation(fProgramID,
+ PositionAttributeIdx(),
+ builder.positionAttribute().c_str()));
for (int t = 0; t < GrDrawState::kMaxTexCoords; ++t) {
if (texCoordAttrNames[t].size()) {
GL_CALL(BindAttribLocation(fProgramID,
@@ -905,7 +877,7 @@ void GrGLProgram::initSamplerUniforms() {
for (int s = 0; s < GrDrawState::kNumStages; ++s) {
int count = fUniforms.fStages[s].fSamplerUniforms.count();
// FIXME: We're still always reserving one texture per stage. After GrTextureParams are
- // expressed by the custom stage rather than the GrSamplerState we can move texture binding
+ // expressed by the effect rather than the GrEffectStage we can move texture binding
// into GrGLProgram and it should be easier to fix this.
GrAssert(count <= 1);
for (int t = 0; t < count; ++t) {
@@ -921,73 +893,53 @@ void GrGLProgram::initSamplerUniforms() {
// Stage code generation
// TODO: Move this function to GrGLShaderBuilder
-GrGLProgramStage* GrGLProgram::GenStageCode(const GrCustomStage* stage,
- const StageDesc& desc,
- StageUniforms* uniforms,
- const char* fsInColor, // NULL means no incoming color
- const char* fsOutColor,
- const char* vsInCoord,
- GrGLShaderBuilder* builder) {
-
- GrGLProgramStage* glStage = stage->getFactory().createGLInstance(*stage);
-
- /// Vertex Shader Stuff
-
- // decide whether we need a matrix to transform texture coords and whether the varying needs a
- // perspective coord.
- const char* matName = NULL;
- GrSLType texCoordVaryingType;
- if (desc.fOptFlags & StageDesc::kIdentityMatrix_OptFlagBit) {
- texCoordVaryingType = kVec2f_GrSLType;
- } else {
- uniforms->fTextureMatrixUni = builder->addUniform(GrGLShaderBuilder::kVertex_ShaderType,
- kMat33f_GrSLType, "TexM", &matName);
- builder->getUniformVariable(uniforms->fTextureMatrixUni);
-
- if (desc.fOptFlags & StageDesc::kNoPerspective_OptFlagBit) {
- texCoordVaryingType = kVec2f_GrSLType;
- } else {
- texCoordVaryingType = kVec3f_GrSLType;
- }
- }
- const char *varyingVSName, *varyingFSName;
- builder->addVarying(texCoordVaryingType,
- "Stage",
- &varyingVSName,
- &varyingFSName);
- builder->setupTextureAccess(varyingFSName, texCoordVaryingType);
-
- // Must setup variables after calling setupTextureAccess
- glStage->setupVariables(builder);
-
- int numTextures = stage->numTextures();
+GrGLEffect* GrGLProgram::GenStageCode(const GrEffectStage& stage,
+ GrGLEffect::EffectKey key,
+ StageUniforms* uniforms,
+ const char* fsInColor, // NULL means no incoming color
+ const char* fsOutColor,
+ const char* vsInCoord,
+ GrGLShaderBuilder* builder) {
+
+ const GrEffect* effect = stage.getEffect();
+ GrGLEffect* glEffect = effect->getFactory().createGLInstance(*effect);
+
+ // setup texture samplers for GL effect
+ int numTextures = effect->numTextures();
SkSTArray<8, GrGLShaderBuilder::TextureSampler> textureSamplers;
-
textureSamplers.push_back_n(numTextures);
-
for (int i = 0; i < numTextures; ++i) {
- textureSamplers[i].init(builder, &stage->textureAccess(i));
+ textureSamplers[i].init(builder, &effect->textureAccess(i));
uniforms->fSamplerUniforms.push_back(textureSamplers[i].fSamplerUniform);
}
- if (!matName) {
- GrAssert(kVec2f_GrSLType == texCoordVaryingType);
- builder->fVSCode.appendf("\t%s = %s;\n", varyingVSName, vsInCoord);
- } else {
- // varying = texMatrix * texCoord
- builder->fVSCode.appendf("\t%s = (%s * vec3(%s, 1))%s;\n",
- varyingVSName, matName, vsInCoord,
- vector_all_coords(GrSLTypeToVecLength(texCoordVaryingType)));
- }
-
- builder->fVSCode.appendf("\t{ // %s\n", glStage->name());
- glStage->emitVS(builder, varyingVSName);
- builder->fVSCode.appendf("\t}\n");
-
// Enclose custom code in a block to avoid namespace conflicts
- builder->fFSCode.appendf("\t{ // %s \n", glStage->name());
- glStage->emitFS(builder, fsOutColor, fsInColor, textureSamplers);
+ builder->fVSCode.appendf("\t{ // %s\n", glEffect->name());
+ builder->fFSCode.appendf("\t{ // %s \n", glEffect->name());
+ glEffect->emitCode(builder,
+ stage,
+ key,
+ vsInCoord,
+ fsOutColor,
+ fsInColor,
+ textureSamplers);
+ builder->fVSCode.appendf("\t}\n");
builder->fFSCode.appendf("\t}\n");
- return glStage;
+ return glEffect;
+}
+
+void GrGLProgram::setData(const GrDrawState& drawState) {
+ int rtHeight = drawState.getRenderTarget()->height();
+ if (GrGLUniformManager::kInvalidUniformHandle != fUniforms.fRTHeight && fRTHeight != rtHeight) {
+ fUniformManager.set1f(fUniforms.fRTHeight, SkIntToScalar(rtHeight));
+ fRTHeight = rtHeight;
+ }
+ for (int s = 0; s < GrDrawState::kNumStages; ++s) {
+ if (NULL != fEffects[s]) {
+ const GrEffectStage& stage = drawState.getStage(s);
+ GrAssert(NULL != stage.getEffect());
+ fEffects[s]->setData(fUniformManager, stage);
+ }
+ }
}
diff --git a/src/gpu/gl/GrGLProgram.h b/src/gpu/gl/GrGLProgram.h
index e51f66351c..3902a8c028 100644
--- a/src/gpu/gl/GrGLProgram.h
+++ b/src/gpu/gl/GrGLProgram.h
@@ -10,6 +10,7 @@
#define GrGLProgram_DEFINED
#include "GrDrawState.h"
+#include "GrGLEffect.h"
#include "GrGLContextInfo.h"
#include "GrGLSL.h"
#include "GrGLTexture.h"
@@ -19,7 +20,7 @@
#include "SkXfermode.h"
class GrBinHashKeyBuilder;
-class GrGLProgramStage;
+class GrGLEffect;
class GrGLShaderBuilder;
// optionally compile the experimental GS code. Set to GR_DEBUG
@@ -43,7 +44,7 @@ public:
static GrGLProgram* Create(const GrGLContextInfo& gl,
const Desc& desc,
- const GrCustomStage** customStages);
+ const GrEffectStage* stages[]);
virtual ~GrGLProgram();
@@ -51,36 +52,32 @@ public:
void abandon();
/**
- * The shader may modify the blend coeffecients. Params are in/out
+ * The shader may modify the blend coefficients. Params are in/out
*/
void overrideBlend(GrBlendCoeff* srcCoeff, GrBlendCoeff* dstCoeff) const;
const Desc& getDesc() { return fDesc; }
/**
- * Attribute indices. These should not overlap. Matrices consume 3 slots.
+ * Attribute indices. These should not overlap.
*/
static int PositionAttributeIdx() { return 0; }
- static int TexCoordAttributeIdx(int tcIdx) { return 1 + tcIdx; }
- static int ColorAttributeIdx() { return 1 + GrDrawState::kMaxTexCoords; }
- static int CoverageAttributeIdx() {
- return 2 + GrDrawState::kMaxTexCoords;
- }
- static int EdgeAttributeIdx() { return 3 + GrDrawState::kMaxTexCoords; }
-
- static int ViewMatrixAttributeIdx() {
- return 4 + GrDrawState::kMaxTexCoords;
- }
- static int TextureMatrixAttributeIdx(int stage) {
- return 7 + GrDrawState::kMaxTexCoords + 3 * stage;
- }
+ static int ColorAttributeIdx() { return 1; }
+ static int CoverageAttributeIdx() { return 2; }
+ static int EdgeAttributeIdx() { return 3; }
+ static int TexCoordAttributeIdx(int tcIdx) { return 4 + tcIdx; }
+
+ /**
+ * This function uploads uniforms and calls each GrEffect's setData. It is called before a draw
+ * occurs using the program after the program has already been bound.
+ */
+ void setData(const GrDrawState& drawState);
// Parameters that affect code generation
- // These structs should be kept compact; they are the input to an
- // expensive hash key generator.
+ // This structs should be kept compact; it is input to an expensive hash key generator.
struct Desc {
Desc() {
- // since we use this as part of a key we can't have any unitialized
+ // since we use this as part of a key we can't have any uninitialized
// padding
memset(this, 0, sizeof(Desc));
}
@@ -90,33 +87,7 @@ public:
return reinterpret_cast<const uint32_t*>(this);
}
- struct StageDesc {
- enum OptFlagBits {
- kNoPerspective_OptFlagBit = 1 << 0,
- kIdentityMatrix_OptFlagBit = 1 << 1,
- kIsEnabled_OptFlagBit = 1 << 7
- };
-
- uint8_t fOptFlags;
-
- /** Non-zero if user-supplied code will write the stage's
- contribution to the fragment shader. */
- GrProgramStageFactory::StageKey fCustomStageKey;
-
- inline bool isEnabled() const {
- return SkToBool(fOptFlags & kIsEnabled_OptFlagBit);
- }
- inline void setEnabled(bool newValue) {
- if (newValue) {
- fOptFlags |= kIsEnabled_OptFlagBit;
- } else {
- fOptFlags &= ~kIsEnabled_OptFlagBit;
- }
- }
- };
-
- // Specifies where the intitial color comes from before the stages are
- // applied.
+ // Specifies where the initial color comes from before the stages are applied.
enum ColorInput {
kSolidWhite_ColorInput,
kTransBlack_ColorInput,
@@ -126,7 +97,7 @@ public:
kColorInputCnt
};
// Dual-src blending makes use of a secondary output color that can be
- // used as a per-pixel blend coeffecient. This controls whether a
+ // used as a per-pixel blend coefficient. This controls whether a
// secondary source is output and what value it holds.
enum DualSrcOutput {
kNone_DualSrcOutput,
@@ -137,12 +108,16 @@ public:
kDualSrcOutputCnt
};
+ // TODO: remove these two members when edge-aa can be rewritten as a GrEffect.
GrDrawState::VertexEdgeType fVertexEdgeType;
+ // should the FS discard if the edge-aa coverage is zero (to avoid stencil manipulation)
+ bool fDiscardIfOutsideEdge;
- // stripped of bits that don't affect prog generation
+ // stripped of bits that don't affect program generation
GrVertexLayout fVertexLayout;
- StageDesc fStages[GrDrawState::kNumStages];
+ /** Non-zero if this stage has an effect */
+ GrGLEffect::EffectKey fEffectKeys[GrDrawState::kNumStages];
// To enable experimental geometry shader code (not for use in
// production)
@@ -155,38 +130,34 @@ public:
uint8_t fDualSrcOutput; // casts to enum DualSrcOutput
int8_t fFirstCoverageStage;
SkBool8 fEmitsPointSize;
- SkBool8 fColorMatrixEnabled;
uint8_t fColorFilterXfermode; // casts to enum SkXfermode::Mode
};
GR_STATIC_ASSERT(!(sizeof(Desc) % 4));
- // for code readability
- typedef Desc::StageDesc StageDesc;
-
private:
struct StageUniforms;
GrGLProgram(const GrGLContextInfo& gl,
const Desc& desc,
- const GrCustomStage** customStages);
+ const GrEffectStage* stages[]);
bool succeeded() const { return 0 != fProgramID; }
/**
- * This is the heavy initilization routine for building a GLProgram.
+ * This is the heavy initialization routine for building a GLProgram.
*/
- bool genProgram(const GrCustomStage** customStages);
+ bool genProgram(const GrEffectStage* stages[]);
void genInputColor(GrGLShaderBuilder* builder, SkString* inColor);
- static GrGLProgramStage* GenStageCode(const GrCustomStage* stage,
- const StageDesc& desc, // TODO: Eliminate this
- StageUniforms* stageUniforms, // TODO: Eliminate this
- const char* fsInColor, // NULL means no incoming color
- const char* fsOutColor,
- const char* vsInCoord,
- GrGLShaderBuilder* builder);
+ static GrGLEffect* GenStageCode(const GrEffectStage& stage,
+ GrGLEffect::EffectKey key,
+ StageUniforms* stageUniforms, // TODO: Eliminate this
+ const char* fsInColor, // NULL means no incoming color
+ const char* fsOutColor,
+ const char* vsInCoord,
+ GrGLShaderBuilder* builder);
void genGeometryShader(GrGLShaderBuilder* segments) const;
@@ -200,7 +171,8 @@ private:
bool genEdgeCoverage(SkString* coverageVar, GrGLShaderBuilder* builder) const;
// Creates a GL program ID, binds shader attributes to GL vertex attrs, and links the program
- bool bindOutputsAttribsAndLinkProgram(SkString texCoordAttrNames[GrDrawState::kMaxTexCoords],
+ bool bindOutputsAttribsAndLinkProgram(const GrGLShaderBuilder& builder,
+ SkString texCoordAttrNames[GrDrawState::kMaxTexCoords],
bool bindColorOut,
bool bindDualSrcOut);
@@ -212,11 +184,7 @@ private:
const char* adjustInColor(const SkString& inColor) const;
struct StageUniforms {
- UniformHandle fTextureMatrixUni;
SkTArray<UniformHandle, true> fSamplerUniforms;
- StageUniforms() {
- fTextureMatrixUni = GrGLUniformManager::kInvalidUniformHandle;
- }
};
struct Uniforms {
@@ -224,16 +192,16 @@ private:
UniformHandle fColorUni;
UniformHandle fCoverageUni;
UniformHandle fColorFilterUni;
- UniformHandle fColorMatrixUni;
- UniformHandle fColorMatrixVecUni;
+ // We use the render target height to provide a y-down frag coord when specifying
+ // origin_upper_left is not supported.
+ UniformHandle fRTHeight;
StageUniforms fStages[GrDrawState::kNumStages];
Uniforms() {
fViewMatrixUni = GrGLUniformManager::kInvalidUniformHandle;
fColorUni = GrGLUniformManager::kInvalidUniformHandle;
fCoverageUni = GrGLUniformManager::kInvalidUniformHandle;
fColorFilterUni = GrGLUniformManager::kInvalidUniformHandle;
- fColorMatrixUni = GrGLUniformManager::kInvalidUniformHandle;
- fColorMatrixVecUni = GrGLUniformManager::kInvalidUniformHandle;
+ fRTHeight = GrGLUniformManager::kInvalidUniformHandle;
}
};
@@ -245,7 +213,7 @@ private:
// The matrix sent to GL is determined by both the client's matrix and
// the size of the viewport.
- GrMatrix fViewMatrix;
+ SkMatrix fViewMatrix;
SkISize fViewportSize;
// these reflect the current values of uniforms
@@ -253,12 +221,9 @@ private:
GrColor fColor;
GrColor fCoverage;
GrColor fColorFilterColor;
- /// When it is sent to GL, the texture matrix will be flipped if the texture orientation
- /// (below) requires.
- GrMatrix fTextureMatrices[GrDrawState::kNumStages];
- GrGLTexture::Orientation fTextureOrientation[GrDrawState::kNumStages];
+ int fRTHeight;
- GrGLProgramStage* fProgramStage[GrDrawState::kNumStages];
+ GrGLEffect* fEffects[GrDrawState::kNumStages];
Desc fDesc;
const GrGLContextInfo& fContextInfo;
diff --git a/src/gpu/gl/GrGLProgramStage.cpp b/src/gpu/gl/GrGLProgramStage.cpp
deleted file mode 100644
index b7d0c656d7..0000000000
--- a/src/gpu/gl/GrGLProgramStage.cpp
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2012 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "GrGLSL.h"
-#include "GrGLProgramStage.h"
-
-GrGLProgramStage::GrGLProgramStage(const GrProgramStageFactory& factory)
- : fFactory(factory) {
-}
-
-GrGLProgramStage::~GrGLProgramStage() {
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
-void GrGLProgramStage::setupVariables(GrGLShaderBuilder*) {
-
-}
-
-void GrGLProgramStage::setData(const GrGLUniformManager&,
- const GrCustomStage&,
- const GrRenderTarget*,
- int stageNum) {
-}
-
-GrGLProgramStage::StageKey GrGLProgramStage::GenTextureKey(const GrCustomStage& stage,
- const GrGLCaps& caps) {
- StageKey key = 0;
- for (int index = 0; index < stage.numTextures(); ++index) {
- const GrTextureAccess& access = stage.textureAccess(index);
- StageKey value = GrGLShaderBuilder::KeyForTextureAccess(access, caps) << index;
- GrAssert(0 == (value & key)); // keys for each access ought not to overlap
- key |= value;
- }
- return key;
-}
diff --git a/src/gpu/gl/GrGLProgramStage.h b/src/gpu/gl/GrGLProgramStage.h
deleted file mode 100644
index 28d3f49386..0000000000
--- a/src/gpu/gl/GrGLProgramStage.h
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 2012 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef GrGLCustomStage_DEFINED
-#define GrGLCustomStage_DEFINED
-
-#include "GrAllocator.h"
-#include "GrCustomStage.h"
-#include "GrGLProgram.h"
-#include "GrGLShaderBuilder.h"
-#include "GrGLShaderVar.h"
-#include "GrGLSL.h"
-
-struct GrGLInterface;
-class GrGLTexture;
-
-/** @file
- This file contains specializations for OpenGL of the shader stages
- declared in src/gpu/GrCustomStage.h. All the functions emit
- GLSL shader code and OpenGL calls.
-
- These objects are created by a factory function on the
- GrCustomStage.
-*/
-
-class GrGLProgramStage {
-
-public:
- typedef GrCustomStage::StageKey StageKey;
- enum {
- // the number of bits in StageKey available to GenKey
- kProgramStageKeyBits = GrProgramStageFactory::kProgramStageKeyBits,
- };
-
- typedef GrGLShaderBuilder::TextureSamplerArray TextureSamplerArray;
-
- GrGLProgramStage(const GrProgramStageFactory&);
-
- virtual ~GrGLProgramStage();
-
- /** Create any uniforms or varyings the vertex shader requires. */
- virtual void setupVariables(GrGLShaderBuilder* builder);
-
- /** Appends vertex code to the appropriate SkString
- on the state.
- The code will be inside an otherwise-empty block.
- Vertex shader input is a vec2 of coordinates, which may
- be altered.
- The code will be inside an otherwise-empty block. */
- virtual void emitVS(GrGLShaderBuilder* builder,
- const char* vertexCoords) = 0;
-
- /** Appends fragment code to the appropriate SkString
- on the state.
- The code will be inside an otherwise-empty block.
- Fragment shader inputs are a vec2 of coordinates, one texture,
- and a color; output is a color. The input color may be NULL which
- indicates that the input color is solid white. TODO: Better system
- for communicating optimization info (e.g. input color is solid white,
- trans black, known to be opaque, etc.) that allows the custom stage
- to communicate back similar known info about its output.
- */
- virtual void emitFS(GrGLShaderBuilder* builder,
- const char* outputColor,
- const char* inputColor,
- const TextureSamplerArray&) = 0;
-
- /** A GrGLCustomStage instance can be reused with any GrCustomStage
- that produces the same stage key; this function reads data from
- a stage and uploads any uniform variables required by the shaders
- created in emit*(). */
- virtual void setData(const GrGLUniformManager&,
- const GrCustomStage& stage,
- const GrRenderTarget* renderTarget,
- int stageNum);
-
- const char* name() const { return fFactory.name(); }
-
- static StageKey GenTextureKey(const GrCustomStage&, const GrGLCaps&);
-
-protected:
-
- const GrProgramStageFactory& fFactory;
-};
-
-#endif
diff --git a/src/gpu/gl/GrGLRenderTarget.cpp b/src/gpu/gl/GrGLRenderTarget.cpp
index 3e72757811..9bbc842e72 100644
--- a/src/gpu/gl/GrGLRenderTarget.cpp
+++ b/src/gpu/gl/GrGLRenderTarget.cpp
@@ -49,7 +49,9 @@ GrGLRenderTarget::GrGLRenderTarget(GrGpuGL* gpu,
texture,
MakeDesc(kNone_GrTextureFlags,
viewport.fWidth, viewport.fHeight,
- desc.fConfig, desc.fSampleCnt)) {
+ desc.fConfig, desc.fSampleCnt),
+ texture->origin()) {
+ GrAssert(kBottomLeft_Origin == texture->origin());
GrAssert(NULL != texID);
GrAssert(NULL != texture);
// FBO 0 can't also be a texture, right?
@@ -70,7 +72,8 @@ GrGLRenderTarget::GrGLRenderTarget(GrGpuGL* gpu,
NULL,
MakeDesc(kNone_GrTextureFlags,
viewport.fWidth, viewport.fHeight,
- desc.fConfig, desc.fSampleCnt)) {
+ desc.fConfig, desc.fSampleCnt),
+ kBottomLeft_Origin) {
this->init(desc, viewport, NULL);
}
diff --git a/src/gpu/gl/GrGLRenderTarget.h b/src/gpu/gl/GrGLRenderTarget.h
index 493e90eea9..b26223ebc3 100644
--- a/src/gpu/gl/GrGLRenderTarget.h
+++ b/src/gpu/gl/GrGLRenderTarget.h
@@ -11,7 +11,7 @@
#include "GrGLIRect.h"
#include "GrRenderTarget.h"
-#include "GrScalar.h"
+#include "SkScalar.h"
class GrGpuGL;
class GrGLTexture;
@@ -51,7 +51,7 @@ public:
const GrGLIRect& getViewport() const { return fViewport; }
// The following two functions return the same ID when a
- // texture-rendertarget is multisampled, and different IDs when
+ // texture/render target is multisampled, and different IDs when
// it is.
// FBO ID used to render into
GrGLuint renderFBOID() const { return fRTFBOID; }
@@ -59,10 +59,10 @@ public:
GrGLuint textureFBOID() const { return fTexFBOID; }
// override of GrRenderTarget
- virtual intptr_t getRenderTargetHandle() const {
+ virtual GrBackendObject getRenderTargetHandle() const {
return this->renderFBOID();
}
- virtual intptr_t getRenderTargetResolvedHandle() const {
+ virtual GrBackendObject getRenderTargetResolvedHandle() const {
return this->textureFBOID();
}
virtual ResolveType getResolveType() const {
@@ -93,7 +93,7 @@ private:
// else own them.
bool fOwnIDs;
- // when we switch to this rendertarget we want to set the viewport to
+ // when we switch to this render target we want to set the viewport to
// only render to to content area (as opposed to the whole allocation) and
// we want the rendering to be at top left (GL has origin in bottom left)
GrGLIRect fViewport;
diff --git a/src/gpu/gl/GrGLSL.cpp b/src/gpu/gl/GrGLSL.cpp
index 420af03a79..64b6c11ada 100644
--- a/src/gpu/gl/GrGLSL.cpp
+++ b/src/gpu/gl/GrGLSL.cpp
@@ -17,6 +17,8 @@ GrGLSLGeneration GrGetGLSLGeneration(GrGLBinding binding,
GrAssert(ver >= GR_GLSL_VER(1,10));
if (ver >= GR_GLSL_VER(1,50)) {
return k150_GrGLSLGeneration;
+ } else if (ver >= GR_GLSL_VER(1,40)) {
+ return k140_GrGLSLGeneration;
} else if (ver >= GR_GLSL_VER(1,30)) {
return k130_GrGLSLGeneration;
} else {
@@ -47,6 +49,9 @@ const char* GrGetGLSLVersionDecl(GrGLBinding binding,
case k130_GrGLSLGeneration:
GrAssert(kDesktop_GrGLBinding == binding);
return "#version 130\n";
+ case k140_GrGLSLGeneration:
+ GrAssert(kDesktop_GrGLBinding == binding);
+ return "#version 140\n";
case k150_GrGLSLGeneration:
GrAssert(kDesktop_GrGLBinding == binding);
return "#version 150\n";
diff --git a/src/gpu/gl/GrGLSL.h b/src/gpu/gl/GrGLSL.h
index cbff273382..c0d3d5e596 100644
--- a/src/gpu/gl/GrGLSL.h
+++ b/src/gpu/gl/GrGLSL.h
@@ -17,7 +17,7 @@ class SkString;
// down the GLSL version to one of these enums.
enum GrGLSLGeneration {
/**
- * Desktop GLSL 1.10 and ES2 shading lang (based on desktop GLSL 1.20)
+ * Desktop GLSL 1.10 and ES2 shading language (based on desktop GLSL 1.20)
*/
k110_GrGLSLGeneration,
/**
@@ -25,7 +25,11 @@ enum GrGLSLGeneration {
*/
k130_GrGLSLGeneration,
/**
- * Dekstop GLSL 1.50
+ * Desktop GLSL 1.40
+ */
+ k140_GrGLSLGeneration,
+ /**
+ * Desktop GLSL 1.50
*/
k150_GrGLSLGeneration,
};
@@ -33,7 +37,7 @@ enum GrGLSLGeneration {
/**
* Types of shader-language-specific boxed variables we can create.
* (Currently only GrGLShaderVars, but should be applicable to other shader
- * langauges.)
+ * languages.)
*/
enum GrSLType {
kVoid_GrSLType,
@@ -90,7 +94,7 @@ GrGLSLGeneration GrGetGLSLGeneration(GrGLBinding binding,
const GrGLInterface* gl);
/**
- * Returns a string to include at the begining of a shader to declare the GLSL
+ * Returns a string to include at the beginning of a shader to declare the GLSL
* version.
*/
const char* GrGetGLSLVersionDecl(GrGLBinding binding,
@@ -136,7 +140,7 @@ const char* GrGLSLVectorNonhomogCoords(GrSLType type);
* vec4(0,0,0,0) is assumed. It is an error to pass kNone for default<i> if in<i> is NULL or "".
* Note that when if function determines that the result is a zeros or ones vec then any expression
* represented by in0 or in1 will not be emitted. The return value indicates whether a zeros, ones
- * or neither was appeneded.
+ * or neither was appended.
*/
GrSLConstantVec GrGLSLModulate4f(SkString* outAppend,
const char* in0,
@@ -146,10 +150,10 @@ GrSLConstantVec GrGLSLModulate4f(SkString* outAppend,
/**
* Does an inplace mul, *=, of vec4VarName by mulFactor. If mulFactorDefault is not kNone then
- * mulFactor may be either "" or NULL. In this case either nothing will be appened (kOnes) or an
+ * mulFactor may be either "" or NULL. In this case either nothing will be appended (kOnes) or an
* assignment of vec(0,0,0,0) will be appended (kZeros). The assignment is prepended by tabCnt tabs.
* A semicolon and newline are added after the assignment. (TODO: Remove tabCnt when we auto-insert
- * tabs to custom stage-generated lines.) If a zeros vec is assigned then the return value is
+ * tabs to GrGLEffect-generated lines.) If a zeros vec is assigned then the return value is
* kZeros, otherwise kNone.
*/
GrSLConstantVec GrGLSLMulVarBy4f(SkString* outAppend,
diff --git a/src/gpu/gl/GrGLShaderBuilder.cpp b/src/gpu/gl/GrGLShaderBuilder.cpp
index 473c7b4d73..8239740128 100644
--- a/src/gpu/gl/GrGLShaderBuilder.cpp
+++ b/src/gpu/gl/GrGLShaderBuilder.cpp
@@ -92,34 +92,12 @@ GrGLShaderBuilder::GrGLShaderBuilder(const GrGLContextInfo& ctx, GrGLUniformMana
, fUsesGS(false)
, fContext(ctx)
, fUniformManager(uniformManager)
- , fCurrentStage(kNonStageIdx)
- , fTexCoordVaryingType(kVoid_GrSLType) {
-}
+ , fCurrentStageIdx(kNonStageIdx)
+ , fSetupFragPosition(false)
+ , fRTHeightUniform(GrGLUniformManager::kInvalidUniformHandle) {
-void GrGLShaderBuilder::setupTextureAccess(const char* varyingFSName, GrSLType varyingType) {
- // FIXME: We don't know how the custom stage will manipulate the coords. So we give up on using
- // projective texturing and always give the stage 2D coords. This will be fixed when custom
- // stages are repsonsible for setting up their own tex coords / tex matrices.
- switch (varyingType) {
- case kVec2f_GrSLType:
- fDefaultTexCoordsName = varyingFSName;
- fTexCoordVaryingType = kVec2f_GrSLType;
- break;
- case kVec3f_GrSLType: {
- fDefaultTexCoordsName = "inCoord";
- GrAssert(kNonStageIdx != fCurrentStage);
- fDefaultTexCoordsName.appendS32(fCurrentStage);
- fTexCoordVaryingType = kVec3f_GrSLType;
- fFSCode.appendf("\t%s %s = %s.xy / %s.z;\n",
- GrGLShaderVar::TypeString(kVec2f_GrSLType),
- fDefaultTexCoordsName.c_str(),
- varyingFSName,
- varyingFSName);
- break;
- }
- default:
- GrCrash("Tex coords must either be Vec2f or Vec3f");
- }
+ fPositionVar = &fVSAttrs.push_back();
+ fPositionVar->set(kVec2f_GrSLType, GrGLShaderVar::kAttribute_TypeModifier, "aPosition");
}
void GrGLShaderBuilder::appendTextureLookup(SkString* out,
@@ -127,11 +105,8 @@ void GrGLShaderBuilder::appendTextureLookup(SkString* out,
const char* coordName,
GrSLType varyingType) const {
GrAssert(NULL != sampler.textureAccess());
+ GrAssert(NULL != coordName);
- if (NULL == coordName) {
- coordName = fDefaultTexCoordsName.c_str();
- varyingType = kVec2f_GrSLType;
- }
out->appendf("%s(%s, %s)",
sample_function_name(varyingType),
this->getUniformCStr(sampler.fSamplerUniform),
@@ -151,9 +126,10 @@ void GrGLShaderBuilder::appendTextureLookupAndModulate(
GrGLSLModulate4f(out, modulation, lookup.c_str());
}
-GrCustomStage::StageKey GrGLShaderBuilder::KeyForTextureAccess(const GrTextureAccess& access,
- const GrGLCaps& caps) {
- GrCustomStage::StageKey key = 0;
+GrBackendEffectFactory::EffectKey GrGLShaderBuilder::KeyForTextureAccess(
+ const GrTextureAccess& access,
+ const GrGLCaps& caps) {
+ GrBackendEffectFactory::EffectKey key = 0;
// Assume that swizzle support implies that we never have to modify a shader to adjust
// for texture format/swizzle settings.
@@ -210,10 +186,10 @@ GrGLUniformManager::UniformHandle GrGLShaderBuilder::addUniformArray(uint32_t vi
uni.fVariable.setType(type);
uni.fVariable.setTypeModifier(GrGLShaderVar::kUniform_TypeModifier);
SkString* uniName = uni.fVariable.accessName();
- if (kNonStageIdx == fCurrentStage) {
+ if (kNonStageIdx == fCurrentStageIdx) {
uniName->printf("u%s", name);
} else {
- uniName->printf("u%s%d", name, fCurrentStage);
+ uniName->printf("u%s%d", name, fCurrentStageIdx);
}
uni.fVariable.setArrayCount(count);
uni.fVisibility = visibility;
@@ -244,10 +220,10 @@ void GrGLShaderBuilder::addVarying(GrSLType type,
fVSOutputs.push_back();
fVSOutputs.back().setType(type);
fVSOutputs.back().setTypeModifier(GrGLShaderVar::kOut_TypeModifier);
- if (kNonStageIdx == fCurrentStage) {
+ if (kNonStageIdx == fCurrentStageIdx) {
fVSOutputs.back().accessName()->printf("v%s", name);
} else {
- fVSOutputs.back().accessName()->printf("v%s%d", name, fCurrentStage);
+ fVSOutputs.back().accessName()->printf("v%s%d", name, fCurrentStageIdx);
}
if (vsOutName) {
*vsOutName = fVSOutputs.back().getName().c_str();
@@ -265,10 +241,10 @@ void GrGLShaderBuilder::addVarying(GrSLType type,
fGSOutputs.push_back();
fGSOutputs.back().setType(type);
fGSOutputs.back().setTypeModifier(GrGLShaderVar::kOut_TypeModifier);
- if (kNonStageIdx == fCurrentStage) {
+ if (kNonStageIdx == fCurrentStageIdx) {
fGSOutputs.back().accessName()->printf("g%s", name);
} else {
- fGSOutputs.back().accessName()->printf("g%s%d", name, fCurrentStage);
+ fGSOutputs.back().accessName()->printf("g%s%d", name, fCurrentStageIdx);
}
fsName = fGSOutputs.back().accessName();
} else {
@@ -283,6 +259,44 @@ void GrGLShaderBuilder::addVarying(GrSLType type,
}
}
+const char* GrGLShaderBuilder::fragmentPosition() {
+ if (fContext.caps().fragCoordConventionsSupport()) {
+ if (!fSetupFragPosition) {
+ fFSHeader.append("#extension GL_ARB_fragment_coord_conventions: require\n");
+ fFSInputs.push_back().set(kVec4f_GrSLType,
+ GrGLShaderVar::kIn_TypeModifier,
+ "gl_FragCoord",
+ GrGLShaderVar::kDefault_Precision,
+ GrGLShaderVar::kUpperLeft_Origin);
+ fSetupFragPosition = true;
+ }
+ return "gl_FragCoord";
+ } else {
+ static const char* kCoordName = "fragCoordYDown";
+ if (!fSetupFragPosition) {
+ GrAssert(GrGLUniformManager::kInvalidUniformHandle == fRTHeightUniform);
+ const char* rtHeightName;
+
+ // temporarily change the stage index because we're inserting a uniform whose name
+ // shouldn't be mangled to be stage-specific.
+ int oldStageIdx = fCurrentStageIdx;
+ fCurrentStageIdx = kNonStageIdx;
+ fRTHeightUniform = this->addUniform(kFragment_ShaderType,
+ kFloat_GrSLType,
+ "RTHeight",
+ &rtHeightName);
+ fCurrentStageIdx = oldStageIdx;
+
+ this->fFSCode.prependf("\tvec4 %s = vec4(gl_FragCoord.x, %s - gl_FragCoord.y, gl_FragCoord.zw);\n",
+ kCoordName, rtHeightName);
+ fSetupFragPosition = true;
+ }
+ GrAssert(GrGLUniformManager::kInvalidUniformHandle != fRTHeightUniform);
+ return kCoordName;
+ }
+}
+
+
void GrGLShaderBuilder::emitFunction(ShaderType shader,
GrSLType returnType,
const char* name,
@@ -292,8 +306,8 @@ void GrGLShaderBuilder::emitFunction(ShaderType shader,
SkString* outName) {
GrAssert(kFragment_ShaderType == shader);
fFSFunctions.append(GrGLShaderVar::TypeString(returnType));
- if (kNonStageIdx != fCurrentStage) {
- outName->printf(" %s_%d", name, fCurrentStage);
+ if (kNonStageIdx != fCurrentStageIdx) {
+ outName->printf(" %s_%d", name, fCurrentStageIdx);
} else {
*outName = name;
}
@@ -359,7 +373,9 @@ void GrGLShaderBuilder::getShader(ShaderType type, SkString* shaderStr) const {
this->appendUniformDecls(kVertex_ShaderType, shaderStr);
this->appendDecls(fVSAttrs, shaderStr);
this->appendDecls(fVSOutputs, shaderStr);
+ shaderStr->append("void main() {\n");
shaderStr->append(fVSCode);
+ shaderStr->append("}\n");
break;
case kGeometry_ShaderType:
if (fUsesGS) {
@@ -367,7 +383,9 @@ void GrGLShaderBuilder::getShader(ShaderType type, SkString* shaderStr) const {
shaderStr->append(fGSHeader);
this->appendDecls(fGSInputs, shaderStr);
this->appendDecls(fGSOutputs, shaderStr);
+ shaderStr->append("void main() {\n");
shaderStr->append(fGSCode);
+ shaderStr->append("}\n");
} else {
shaderStr->reset();
}
@@ -377,13 +395,16 @@ void GrGLShaderBuilder::getShader(ShaderType type, SkString* shaderStr) const {
append_default_precision_qualifier(kDefaultFragmentPrecision,
fContext.binding(),
shaderStr);
+ shaderStr->append(fFSHeader);
this->appendUniformDecls(kFragment_ShaderType, shaderStr);
this->appendDecls(fFSInputs, shaderStr);
// We shouldn't have declared outputs on 1.10
GrAssert(k110_GrGLSLGeneration != fContext.glslGeneration() || fFSOutputs.empty());
this->appendDecls(fFSOutputs, shaderStr);
shaderStr->append(fFSFunctions);
+ shaderStr->append("void main() {\n");
shaderStr->append(fFSCode);
+ shaderStr->append("}\n");
break;
}
}
diff --git a/src/gpu/gl/GrGLShaderBuilder.h b/src/gpu/gl/GrGLShaderBuilder.h
index f143af39a7..852079a09e 100644
--- a/src/gpu/gl/GrGLShaderBuilder.h
+++ b/src/gpu/gl/GrGLShaderBuilder.h
@@ -9,7 +9,8 @@
#define GrGLShaderBuilder_DEFINED
#include "GrAllocator.h"
-#include "GrCustomStage.h"
+#include "GrBackendEffectFactory.h"
+#include "GrEffect.h"
#include "gl/GrGLShaderVar.h"
#include "gl/GrGLSL.h"
#include "gl/GrGLUniformManager.h"
@@ -23,7 +24,7 @@ class GrGLContextInfo;
class GrGLShaderBuilder {
public:
/**
- * Used by GrGLProgramStages to add texture reads to their shader code.
+ * Used by GrGLEffects to add texture reads to their shader code.
*/
class TextureSampler {
public:
@@ -76,18 +77,12 @@ public:
GrGLShaderBuilder(const GrGLContextInfo&, GrGLUniformManager&);
- /** Determines whether we should use texture2D() or texture2Dproj(), and if an explicit divide
- is required for the sample coordinates, creates the new variable and emits the code to
- initialize it. This should only be called by GrGLProgram.*/
- void setupTextureAccess(const char* varyingFSName, GrSLType varyingType);
-
- /** Appends a texture sample with projection if necessary; if coordName is not
- specified, uses fSampleCoords. coordType must either be Vec2f or Vec3f. The latter is
- interpreted as projective texture coords. The vec length and swizzle order of the result
- depends on the GrTextureAccess associated with the TextureSampler. */
+ /** Appends a 2D texture sample with projection if necessary. coordType must either be Vec2f or
+ Vec3f. The latter is interpreted as projective texture coords. The vec length and swizzle
+ order of the result depends on the GrTextureAccess associated with the TextureSampler. */
void appendTextureLookup(SkString* out,
const TextureSampler&,
- const char* coordName = NULL,
+ const char* coordName,
GrSLType coordType = kVec2f_GrSLType) const;
/** Does the work of appendTextureLookup and modulates the result by modulation. The result is
@@ -97,18 +92,9 @@ public:
void appendTextureLookupAndModulate(SkString* out,
const char* modulation,
const TextureSampler&,
- const char* coordName = NULL,
+ const char* coordName,
GrSLType coordType = kVec2f_GrSLType) const;
- /** Gets the name of the default texture coords which are always kVec2f */
- const char* defaultTexCoordsName() const { return fDefaultTexCoordsName.c_str(); }
-
- /* Returns true if the texture matrix from which the default texture coords are computed has
- perspective. */
- bool defaultTextureMatrixIsPerspective() const {
- return fTexCoordVaryingType == kVec3f_GrSLType;
- }
-
/** Emits a helper function outside of main(). Currently ShaderType must be
kFragment_ShaderType. */
void emitFunction(ShaderType shader,
@@ -119,17 +105,17 @@ public:
const char* body,
SkString* outName);
- /** Generates a StageKey for the shader code based on the texture access parameters and the
+ /** Generates a EffectKey for the shader code based on the texture access parameters and the
capabilities of the GL context. This is useful for keying the shader programs that may
have multiple representations, based on the type/format of textures used. */
- static GrCustomStage::StageKey KeyForTextureAccess(const GrTextureAccess& access,
- const GrGLCaps& caps);
+ static GrBackendEffectFactory::EffectKey KeyForTextureAccess(const GrTextureAccess&,
+ const GrGLCaps&);
/** If texture swizzling is available using tex parameters then it is preferred over mangling
the generated shader code. This potentially allows greater reuse of cached shaders. */
static const GrGLenum* GetTexParamSwizzle(GrPixelConfig config, const GrGLCaps& caps);
- /** Add a uniform variable to the current program, that has visibilty in one or more shaders.
+ /** Add a uniform variable to the current program, that has visibility in one or more shaders.
visibility is a bitfield of ShaderType values indicating from which shaders the uniform
should be accessible. At least one bit must be set. Geometry shader uniforms are not
supported at this time. The actual uniform name will be mangled. If outName is not NULL then
@@ -151,7 +137,7 @@ public:
const GrGLShaderVar& getUniformVariable(GrGLUniformManager::UniformHandle) const;
/**
- * Shorcut for getUniformVariable(u).c_str()
+ * Shortcut for getUniformVariable(u).c_str()
*/
const char* getUniformCStr(GrGLUniformManager::UniformHandle u) const {
return this->getUniformVariable(u).c_str();
@@ -165,20 +151,31 @@ public:
const char** vsOutName = NULL,
const char** fsInName = NULL);
+ /** Returns a variable name that represents the position of the fragment in the FS. The position
+ is in device space (e.g. 0,0 is the top left and pixel centers are at half-integers). */
+ const char* fragmentPosition();
+
+ /** Returns a vertex attribute that represents the vertex position in the VS. This is the
+ pre-matrix position and is commonly used by effects to compute texture coords via a matrix.
+ */
+ const GrGLShaderVar& positionAttribute() const { return *fPositionVar; }
+
/** Called after building is complete to get the final shader string. */
void getShader(ShaderType, SkString*) const;
/**
- * TODO: Make this do all the compiling, linking, etc. Hide from the custom stages
+ * TODO: Make this do all the compiling, linking, etc. Hide from the GrEffects
*/
void finished(GrGLuint programID);
/**
* Sets the current stage (used to make variable names unique).
- * TODO: Hide from the custom stages
+ * TODO: Hide from the GrEffects
*/
- void setCurrentStage(int stage) { fCurrentStage = stage; }
- void setNonStage() { fCurrentStage = kNonStageIdx; }
+ void setCurrentStage(int stageIdx) { fCurrentStageIdx = stageIdx; }
+ void setNonStage() { fCurrentStageIdx = kNonStageIdx; }
+
+ GrGLUniformManager::UniformHandle getRTHeightUniform() const { return fRTHeightUniform; }
private:
@@ -211,18 +208,16 @@ private:
kNonStageIdx = -1,
};
- const GrGLContextInfo& fContext;
- GrGLUniformManager& fUniformManager;
- int fCurrentStage;
- SkString fFSFunctions;
+ const GrGLContextInfo& fContext;
+ GrGLUniformManager& fUniformManager;
+ int fCurrentStageIdx;
+ SkString fFSFunctions;
+ SkString fFSHeader;
- /// Per-stage settings - only valid while we're inside GrGLProgram::genStageCode().
- //@{
- GrSLType fTexCoordVaryingType; // the type, either Vec2f or Vec3f, of the coords passed
- // as a varying from the VS to the FS.
- SkString fDefaultTexCoordsName; // the name of the default 2D coords value.
- //@}
+ bool fSetupFragPosition;
+ GrGLUniformManager::UniformHandle fRTHeightUniform;
+ GrGLShaderVar* fPositionVar;
};
#endif
diff --git a/src/gpu/gl/GrGLShaderVar.h b/src/gpu/gl/GrGLShaderVar.h
index e8f491c511..89fa087460 100644
--- a/src/gpu/gl/GrGLShaderVar.h
+++ b/src/gpu/gl/GrGLShaderVar.h
@@ -45,6 +45,14 @@ public:
};
/**
+ * See GL_ARB_fragment_coord_conventions.
+ */
+ enum Origin {
+ kDefault_Origin, // when set to kDefault the origin field is ignored.
+ kUpperLeft_Origin, // only used to declare vec4 in gl_FragCoord.
+ };
+
+ /**
* Defaults to a float with no precision specifier
*/
GrGLShaderVar() {
@@ -52,6 +60,7 @@ public:
fTypeModifier = kNone_TypeModifier;
fCount = kNonArray;
fPrecision = kDefault_Precision;
+ fOrigin = kDefault_Origin;
fUseUniformFloatArrays = USE_UNIFORM_FLOAT_ARRAYS;
}
@@ -61,6 +70,7 @@ public:
fTypeModifier = kNone_TypeModifier;
fCount = arrayCount;
fPrecision = kDefault_Precision;
+ fOrigin = kDefault_Origin;
fUseUniformFloatArrays = USE_UNIFORM_FLOAT_ARRAYS;
fName = name;
}
@@ -71,6 +81,7 @@ public:
, fName(var.fName)
, fCount(var.fCount)
, fPrecision(var.fPrecision)
+ , fOrigin(var.fOrigin)
, fUseUniformFloatArrays(var.fUseUniformFloatArrays) {
GrAssert(kVoid_GrSLType != var.fType);
}
@@ -90,6 +101,7 @@ public:
TypeModifier typeModifier,
const SkString& name,
Precision precision = kDefault_Precision,
+ Origin origin = kDefault_Origin,
bool useUniformFloatArrays = USE_UNIFORM_FLOAT_ARRAYS) {
GrAssert(kVoid_GrSLType != type);
fType = type;
@@ -97,6 +109,7 @@ public:
fName = name;
fCount = kNonArray;
fPrecision = precision;
+ fOrigin = origin;
fUseUniformFloatArrays = useUniformFloatArrays;
}
@@ -107,6 +120,7 @@ public:
TypeModifier typeModifier,
const char* name,
Precision precision = kDefault_Precision,
+ Origin origin = kDefault_Origin,
bool useUniformFloatArrays = USE_UNIFORM_FLOAT_ARRAYS) {
GrAssert(kVoid_GrSLType != type);
fType = type;
@@ -114,6 +128,7 @@ public:
fName = name;
fCount = kNonArray;
fPrecision = precision;
+ fOrigin = origin;
fUseUniformFloatArrays = useUniformFloatArrays;
}
@@ -125,6 +140,7 @@ public:
const SkString& name,
int count,
Precision precision = kDefault_Precision,
+ Origin origin = kDefault_Origin,
bool useUniformFloatArrays = USE_UNIFORM_FLOAT_ARRAYS) {
GrAssert(kVoid_GrSLType != type);
fType = type;
@@ -132,6 +148,7 @@ public:
fName = name;
fCount = count;
fPrecision = precision;
+ fOrigin = origin;
fUseUniformFloatArrays = useUniformFloatArrays;
}
@@ -143,6 +160,7 @@ public:
const char* name,
int count,
Precision precision = kDefault_Precision,
+ Origin origin = kDefault_Origin,
bool useUniformFloatArrays = USE_UNIFORM_FLOAT_ARRAYS) {
GrAssert(kVoid_GrSLType != type);
fType = type;
@@ -150,6 +168,7 @@ public:
fName = name;
fCount = count;
fPrecision = precision;
+ fOrigin = origin;
fUseUniformFloatArrays = useUniformFloatArrays;
}
@@ -221,9 +240,24 @@ public:
void setPrecision(Precision p) { fPrecision = p; }
/**
+ * Get the origin of the var
+ */
+ Origin getOrigin() const { return fOrigin; }
+
+ /**
+ * Set the origin of the var
+ */
+ void setOrigin(Origin origin) { fOrigin = origin; }
+
+ /**
* Write a declaration of this variable to out.
*/
void appendDecl(const GrGLContextInfo& gl, SkString* out) const {
+ if (kUpperLeft_Origin == fOrigin) {
+ // this is the only place where we specify a layout modifier. If we use other layout
+ // modifiers in the future then they should be placed in a list.
+ out->append("layout(origin_upper_left) ");
+ }
if (this->getTypeModifier() != kNone_TypeModifier) {
out->append(TypeModifierString(this->getTypeModifier(),
gl.glslGeneration()));
@@ -331,6 +365,7 @@ private:
SkString fName;
int fCount;
Precision fPrecision;
+ Origin fOrigin;
/// Work around driver bugs on some hardware that don't correctly
/// support uniform float []
bool fUseUniformFloatArrays;
diff --git a/src/gpu/gl/GrGLTexture.cpp b/src/gpu/gl/GrGLTexture.cpp
index 1e34fe56a2..6c815e8a33 100644
--- a/src/gpu/gl/GrGLTexture.cpp
+++ b/src/gpu/gl/GrGLTexture.cpp
@@ -26,10 +26,9 @@ void GrGLTexture::init(GrGpuGL* gpu,
(GPUGL->glInterface(),
textureDesc.fTextureID,
textureDesc.fOwnsID));
- fOrientation = textureDesc.fOrientation;
if (NULL != rtDesc) {
- // we render to the top left
+ GrAssert(kBottomLeft_Origin == textureDesc.fOrigin);
GrGLIRect vp;
vp.fLeft = 0;
vp.fWidth = textureDesc.fWidth;
@@ -43,14 +42,14 @@ void GrGLTexture::init(GrGpuGL* gpu,
GrGLTexture::GrGLTexture(GrGpuGL* gpu,
const Desc& textureDesc)
- : INHERITED(gpu, textureDesc) {
+ : INHERITED(gpu, textureDesc, textureDesc.fOrigin) {
this->init(gpu, textureDesc, NULL);
}
GrGLTexture::GrGLTexture(GrGpuGL* gpu,
const Desc& textureDesc,
const GrGLRenderTarget::Desc& rtDesc)
- : INHERITED(gpu, textureDesc) {
+ : INHERITED(gpu, textureDesc, textureDesc.fOrigin) {
this->init(gpu, textureDesc, &rtDesc);
}
@@ -72,7 +71,7 @@ void GrGLTexture::onAbandon() {
INHERITED::onAbandon();
}
-intptr_t GrGLTexture::getTextureHandle() const {
+GrBackendObject GrGLTexture::getTextureHandle() const {
return fTexIDObj->id();
}
diff --git a/src/gpu/gl/GrGLTexture.h b/src/gpu/gl/GrGLTexture.h
index 2e9ee173c8..4666bfbdf8 100644
--- a/src/gpu/gl/GrGLTexture.h
+++ b/src/gpu/gl/GrGLTexture.h
@@ -48,11 +48,6 @@ private:
class GrGLTexture : public GrTexture {
public:
- enum Orientation {
- kBottomUp_Orientation,
- kTopDown_Orientation,
- };
-
struct TexParams {
GrGLenum fFilter;
GrGLenum fWrapS;
@@ -64,7 +59,7 @@ public:
struct Desc : public GrTextureDesc {
GrGLuint fTextureID;
bool fOwnsID;
- Orientation fOrientation;
+ Origin fOrigin;
};
// creates a texture that is also an RT
@@ -79,7 +74,7 @@ public:
virtual ~GrGLTexture() { this->release(); }
- virtual intptr_t getTextureHandle() const SK_OVERRIDE;
+ virtual GrBackendObject getTextureHandle() const SK_OVERRIDE;
virtual void invalidateCachedState() SK_OVERRIDE { fTexParams.invalidate(); }
@@ -95,16 +90,6 @@ public:
}
GrGLuint textureID() const { return fTexIDObj->id(); }
- // Ganesh assumes texture coordinates have their origin
- // in the top-left corner of the image. OpenGL, however,
- // has the origin in the lower-left corner. For content that
- // is loaded by Ganesh we just push the content "upside down"
- // (by GL's understanding of the world) in glTex*Image and the
- // addressing just works out. However, content generated by GL
- // (FBO or externally imported texture) will be updside down
- // and it is up to the GrGpuGL derivative to handle y-mirroing.
- Orientation orientation() const { return fOrientation; }
-
protected:
// overrides of GrTexture
@@ -115,7 +100,6 @@ private:
TexParams fTexParams;
GrGpu::ResetTimestamp fTexParamsTimestamp;
GrGLTexID* fTexIDObj;
- Orientation fOrientation;
void init(GrGpuGL* gpu,
const Desc& textureDesc,
diff --git a/src/gpu/gl/GrGLUniformManager.cpp b/src/gpu/gl/GrGLUniformManager.cpp
index 06ce2db6ed..da6726bb24 100644
--- a/src/gpu/gl/GrGLUniformManager.cpp
+++ b/src/gpu/gl/GrGLUniformManager.cpp
@@ -8,6 +8,7 @@
#include "gl/GrGLShaderBuilder.h"
#include "gl/GrGLProgram.h"
#include "gl/GrGLUniformHandle.h"
+#include "SkMatrix.h"
#define ASSERT_ARRAY_UPLOAD_IN_BOUNDS(UNI, OFFSET, COUNT) \
GrAssert(offset + arrayCount <= uni.fArrayCount || \
@@ -170,7 +171,7 @@ void GrGLUniformManager::setMatrix3f(UniformHandle u, const GrGLfloat matrix[])
const Uniform& uni = fUniforms[handle_to_index(u)];
GrAssert(uni.fType == kMat33f_GrSLType);
GrAssert(GrGLShaderVar::kNonArray == uni.fArrayCount);
- // TODO: Re-enable this assert once texture matrices aren't forced on all custom effects
+ // TODO: Re-enable this assert once texture matrices aren't forced on all effects
// GrAssert(kUnusedUniform != uni.fFSLocation || kUnusedUniform != uni.fVSLocation);
if (kUnusedUniform != uni.fFSLocation) {
GR_GL_CALL(fContext.interface(), UniformMatrix3fv(uni.fFSLocation, 1, false, matrix));
@@ -231,6 +232,23 @@ void GrGLUniformManager::setMatrix4fv(UniformHandle u,
}
}
+void GrGLUniformManager::setSkMatrix(UniformHandle u, const SkMatrix& matrix) const {
+// GR_STATIC_ASSERT(SK_SCALAR_IS_FLOAT);
+ GrGLfloat mt[] = {
+ matrix.get(SkMatrix::kMScaleX),
+ matrix.get(SkMatrix::kMSkewY),
+ matrix.get(SkMatrix::kMPersp0),
+ matrix.get(SkMatrix::kMSkewX),
+ matrix.get(SkMatrix::kMScaleY),
+ matrix.get(SkMatrix::kMPersp1),
+ matrix.get(SkMatrix::kMTransX),
+ matrix.get(SkMatrix::kMTransY),
+ matrix.get(SkMatrix::kMPersp2),
+ };
+ this->setMatrix3f(u, mt);
+}
+
+
void GrGLUniformManager::getUniformLocations(GrGLuint programID, const BuilderUniformArray& uniforms) {
GrAssert(uniforms.count() == fUniforms.count());
int count = fUniforms.count();
diff --git a/src/gpu/gl/GrGLUniformManager.h b/src/gpu/gl/GrGLUniformManager.h
index e9856c6ea3..8f435d3037 100644
--- a/src/gpu/gl/GrGLUniformManager.h
+++ b/src/gpu/gl/GrGLUniformManager.h
@@ -15,6 +15,7 @@
#include "SkTArray.h"
class GrGLContextInfo;
+class SkMatrix;
/** Manages a program's uniforms.
*/
@@ -47,6 +48,9 @@ public:
void setMatrix3fv(UniformHandle, int offset, int arrayCount, const GrGLfloat matrices[]) const;
void setMatrix4fv(UniformHandle, int offset, int arrayCount, const GrGLfloat matrices[]) const;
+ // convenience method for uploading a SkMatrix to a 3x3 matrix uniform
+ void setSkMatrix(UniformHandle, const SkMatrix&) const;
+
struct BuilderUniform {
GrGLShaderVar fVariable;
uint32_t fVisibility;
diff --git a/src/gpu/gl/GrGLUtil.cpp b/src/gpu/gl/GrGLUtil.cpp
index 0e9e21fb2f..55236aab14 100644
--- a/src/gpu/gl/GrGLUtil.cpp
+++ b/src/gpu/gl/GrGLUtil.cpp
@@ -166,6 +166,16 @@ bool GrGLHasExtensionFromString(const char* ext, const char* extensionString) {
return false;
}
+GrGLVendor GrGLGetVendorFromString(const char* vendorString) {
+ if (NULL != vendorString) {
+ if (0 == strcmp(vendorString, "Intel")) {
+ return kIntel_GrGLVendor;
+ }
+ }
+
+ return kOther_GrGLVendor;
+}
+
bool GrGLHasExtension(const GrGLInterface* gl, const char* ext) {
const GrGLubyte* glstr;
GR_GL_CALL_RET(gl, glstr, GetString(GR_GL_EXTENSIONS));
@@ -189,3 +199,10 @@ GrGLSLVersion GrGLGetGLSLVersion(const GrGLInterface* gl) {
GR_GL_CALL_RET(gl, v, GetString(GR_GL_SHADING_LANGUAGE_VERSION));
return GrGLGetGLSLVersionFromString((const char*) v);
}
+
+GrGLVendor GrGLGetVendor(const GrGLInterface* gl) {
+ const GrGLubyte* v;
+ GR_GL_CALL_RET(gl, v, GetString(GR_GL_VENDOR));
+ return GrGLGetVendorFromString((const char*) v);
+}
+
diff --git a/src/gpu/gl/GrGLUtil.h b/src/gpu/gl/GrGLUtil.h
index 17d5a6320e..997207a1c7 100644
--- a/src/gpu/gl/GrGLUtil.h
+++ b/src/gpu/gl/GrGLUtil.h
@@ -16,6 +16,14 @@
typedef uint32_t GrGLVersion;
typedef uint32_t GrGLSLVersion;
+/**
+ * This list is lazily updated as required.
+ */
+enum GrGLVendor {
+ kIntel_GrGLVendor,
+ kOther_GrGLVendor,
+};
+
#define GR_GL_VER(major, minor) ((static_cast<int>(major) << 16) | \
static_cast<int>(minor))
#define GR_GLSL_VER(major, minor) ((static_cast<int>(major) << 16) | \
@@ -61,12 +69,14 @@ GrGLVersion GrGLGetVersionFromString(const char* versionString);
GrGLBinding GrGLGetBindingInUseFromString(const char* versionString);
GrGLSLVersion GrGLGetGLSLVersionFromString(const char* versionString);
bool GrGLHasExtensionFromString(const char* ext, const char* extensionString);
+GrGLVendor GrGLGetVendorFromString(const char* vendorString);
// these variants call glGetString()
bool GrGLHasExtension(const GrGLInterface*, const char* ext);
GrGLBinding GrGLGetBindingInUse(const GrGLInterface*);
GrGLVersion GrGLGetVersion(const GrGLInterface*);
GrGLSLVersion GrGLGetGLSLVersion(const GrGLInterface*);
+GrGLVendor GrGLGetVendor(const GrGLInterface*);
/**
* Helpers for glGetError()
diff --git a/src/gpu/gl/GrGpuGL.cpp b/src/gpu/gl/GrGpuGL.cpp
index 157d9bc2ec..f01fd3f082 100644
--- a/src/gpu/gl/GrGpuGL.cpp
+++ b/src/gpu/gl/GrGpuGL.cpp
@@ -476,13 +476,22 @@ void GrGpuGL::onResetContext() {
fHWConstAttribCoverage = GrColor_ILLEGAL;
}
-GrTexture* GrGpuGL::onCreatePlatformTexture(const GrPlatformTextureDesc& desc) {
- GrGLTexture::Desc glTexDesc;
- if (!configToGLFormats(desc.fConfig, false, NULL, NULL, NULL)) {
+GrTexture* GrGpuGL::onWrapBackendTexture(const GrBackendTextureDesc& desc) {
+ if (!this->configToGLFormats(desc.fConfig, false, NULL, NULL, NULL)) {
return NULL;
}
- // next line relies on PlatformTextureDesc's flags matching GrTexture's
+ if (0 == desc.fTextureHandle) {
+ return NULL;
+ }
+
+ int maxSize = this->getCaps().maxTextureSize();
+ if (desc.fWidth > maxSize || desc.fHeight > maxSize) {
+ return NULL;
+ }
+
+ GrGLTexture::Desc glTexDesc;
+ // next line relies on GrBackendTextureDesc's flags matching GrTexture's
glTexDesc.fFlags = (GrTextureFlags) desc.fFlags;
glTexDesc.fWidth = desc.fWidth;
glTexDesc.fHeight = desc.fHeight;
@@ -490,10 +499,10 @@ GrTexture* GrGpuGL::onCreatePlatformTexture(const GrPlatformTextureDesc& desc) {
glTexDesc.fSampleCnt = desc.fSampleCnt;
glTexDesc.fTextureID = static_cast<GrGLuint>(desc.fTextureHandle);
glTexDesc.fOwnsID = false;
- glTexDesc.fOrientation = GrGLTexture::kBottomUp_Orientation;
+ glTexDesc.fOrigin = GrSurface::kBottomLeft_Origin;
GrGLTexture* texture = NULL;
- if (desc.fFlags & kRenderTarget_GrPlatformTextureFlag) {
+ if (desc.fFlags & kRenderTarget_GrBackendTextureFlag) {
GrGLRenderTarget::Desc glRTDesc;
glRTDesc.fRTFBOID = 0;
glRTDesc.fTexFBOID = 0;
@@ -519,7 +528,7 @@ GrTexture* GrGpuGL::onCreatePlatformTexture(const GrPlatformTextureDesc& desc) {
return texture;
}
-GrRenderTarget* GrGpuGL::onCreatePlatformRenderTarget(const GrPlatformRenderTargetDesc& desc) {
+GrRenderTarget* GrGpuGL::onWrapBackendRenderTarget(const GrBackendRenderTargetDesc& desc) {
GrGLRenderTarget::Desc glDesc;
glDesc.fConfig = desc.fConfig;
glDesc.fRTFBOID = static_cast<GrGLuint>(desc.fRenderTargetHandle);
@@ -574,7 +583,7 @@ void GrGpuGL::onWriteTexturePixels(GrTexture* texture,
desc.fConfig = glTex->config();
desc.fSampleCnt = glTex->desc().fSampleCnt;
desc.fTextureID = glTex->textureID();
- desc.fOrientation = glTex->orientation();
+ desc.fOrigin = glTex->origin();
this->uploadTexData(desc, false,
left, top, width, height,
@@ -665,7 +674,7 @@ bool GrGpuGL::uploadTexData(const GrGLTexture::Desc& desc,
bool swFlipY = false;
bool glFlipY = false;
if (NULL != data) {
- if (GrGLTexture::kBottomUp_Orientation == desc.fOrientation) {
+ if (GrSurface::kBottomLeft_Origin == desc.fOrigin) {
if (this->glCaps().unpackFlipYSupport()) {
glFlipY = true;
} else {
@@ -948,8 +957,7 @@ GrTexture* GrGpuGL::onCreateTexture(const GrTextureDesc& desc,
// We keep GrRenderTargets in GL's normal orientation so that they
// can be drawn to by the outside world without the client having
// to render upside down.
- glTexDesc.fOrientation = renderTarget ? GrGLTexture::kBottomUp_Orientation :
- GrGLTexture::kTopDown_Orientation;
+ glTexDesc.fOrigin = renderTarget ? GrSurface::kBottomLeft_Origin : GrSurface::kTopLeft_Origin;
glRTDesc.fSampleCnt = desc.fSampleCnt;
if (GrGLCaps::kNone_MSFBOType == this->glCaps().msFBOType() &&
@@ -1673,13 +1681,13 @@ const GrStencilSettings& even_odd_nv_path_stencil_settings() {
void GrGpuGL::setStencilPathSettings(const GrPath&,
- GrPathFill fill,
+ SkPath::FillType fill,
GrStencilSettings* settings) {
switch (fill) {
- case kEvenOdd_GrPathFill:
+ case SkPath::kEvenOdd_FillType:
*settings = even_odd_nv_path_stencil_settings();
return;
- case kWinding_GrPathFill:
+ case SkPath::kWinding_FillType:
*settings = winding_nv_path_stencil_settings();
return;
default:
@@ -1687,7 +1695,7 @@ void GrGpuGL::setStencilPathSettings(const GrPath&,
}
}
-void GrGpuGL::onGpuStencilPath(const GrPath* path, GrPathFill fill) {
+void GrGpuGL::onGpuStencilPath(const GrPath* path, SkPath::FillType fill) {
GrAssert(fCaps.pathStencilingSupport());
GrGLuint id = static_cast<const GrGLPath*>(path)->pathID();
@@ -1703,14 +1711,14 @@ void GrGpuGL::onGpuStencilPath(const GrPath* path, GrPathFill fill) {
GrAssert(!fStencilSettings.isTwoSided());
GrGLenum fillMode;
switch (fill) {
- case kWinding_GrPathFill:
+ case SkPath::kWinding_FillType:
fillMode = GR_GL_COUNT_UP;
GrAssert(kIncClamp_StencilOp ==
fStencilSettings.passOp(GrStencilSettings::kFront_Face));
GrAssert(kIncClamp_StencilOp ==
fStencilSettings.failOp(GrStencilSettings::kFront_Face));
break;
- case kEvenOdd_GrPathFill:
+ case SkPath::kEvenOdd_FillType:
fillMode = GR_GL_INVERT;
GrAssert(kInvert_StencilOp ==
fStencilSettings.passOp(GrStencilSettings::kFront_Face));
@@ -2016,18 +2024,20 @@ inline GrGLenum tile_to_gl_wrap(SkShader::TileMode tm) {
}
-void GrGpuGL::flushBoundTextureAndParams(int stage) {
+void GrGpuGL::flushBoundTextureAndParams(int stageIdx) {
GrDrawState* drawState = this->drawState();
- // FIXME: Assuming at most one texture per custom stage
- const GrCustomStage* customStage = drawState->sampler(stage)->getCustomStage();
- GrGLTexture* nextTexture = static_cast<GrGLTexture*>(customStage->texture(0));
- if (NULL != nextTexture) {
- const GrTextureParams& texParams = customStage->textureAccess(0).getParams();
- this->flushBoundTextureAndParams(stage, texParams, nextTexture);
+ // FIXME: Assuming at most one texture per effect
+ const GrEffect* effect = drawState->stage(stageIdx)->getEffect();
+ if (effect->numTextures() > 0) {
+ GrGLTexture* nextTexture = static_cast<GrGLTexture*>(effect->texture(0));
+ if (NULL != nextTexture) {
+ const GrTextureParams& texParams = effect->textureAccess(0).getParams();
+ this->flushBoundTextureAndParams(stageIdx, texParams, nextTexture);
+ }
}
}
-void GrGpuGL::flushBoundTextureAndParams(int stage,
+void GrGpuGL::flushBoundTextureAndParams(int stageIdx,
const GrTextureParams& params,
GrGLTexture* nextTexture) {
@@ -2041,11 +2051,11 @@ void GrGpuGL::flushBoundTextureAndParams(int stage,
this->onResolveRenderTarget(texRT);
}
- if (fHWBoundTextures[stage] != nextTexture) {
- this->setTextureUnit(stage);
+ if (fHWBoundTextures[stageIdx] != nextTexture) {
+ this->setTextureUnit(stageIdx);
GL_CALL(BindTexture(GR_GL_TEXTURE_2D, nextTexture->textureID()));
//GrPrintf("---- bindtexture %d\n", nextTexture->textureID());
- fHWBoundTextures[stage] = nextTexture;
+ fHWBoundTextures[stageIdx] = nextTexture;
}
ResetTimestamp timestamp;
@@ -2062,7 +2072,7 @@ void GrGpuGL::flushBoundTextureAndParams(int stage,
GrGLShaderBuilder::GetTexParamSwizzle(nextTexture->config(), this->glCaps()),
sizeof(newTexParams.fSwizzleRGBA));
if (setAll || newTexParams.fFilter != oldTexParams.fFilter) {
- this->setTextureUnit(stage);
+ this->setTextureUnit(stageIdx);
GL_CALL(TexParameteri(GR_GL_TEXTURE_2D,
GR_GL_TEXTURE_MAG_FILTER,
newTexParams.fFilter));
@@ -2071,13 +2081,13 @@ void GrGpuGL::flushBoundTextureAndParams(int stage,
newTexParams.fFilter));
}
if (setAll || newTexParams.fWrapS != oldTexParams.fWrapS) {
- this->setTextureUnit(stage);
+ this->setTextureUnit(stageIdx);
GL_CALL(TexParameteri(GR_GL_TEXTURE_2D,
GR_GL_TEXTURE_WRAP_S,
newTexParams.fWrapS));
}
if (setAll || newTexParams.fWrapT != oldTexParams.fWrapT) {
- this->setTextureUnit(stage);
+ this->setTextureUnit(stageIdx);
GL_CALL(TexParameteri(GR_GL_TEXTURE_2D,
GR_GL_TEXTURE_WRAP_T,
newTexParams.fWrapT));
@@ -2086,7 +2096,7 @@ void GrGpuGL::flushBoundTextureAndParams(int stage,
(setAll || memcmp(newTexParams.fSwizzleRGBA,
oldTexParams.fSwizzleRGBA,
sizeof(newTexParams.fSwizzleRGBA)))) {
- this->setTextureUnit(stage);
+ this->setTextureUnit(stageIdx);
set_tex_swizzle(newTexParams.fSwizzleRGBA,
this->glInterface());
}
diff --git a/src/gpu/gl/GrGpuGL.h b/src/gpu/gl/GrGpuGL.h
index d2a22d0188..33834cf709 100644
--- a/src/gpu/gl/GrGpuGL.h
+++ b/src/gpu/gl/GrGpuGL.h
@@ -66,10 +66,8 @@ protected:
virtual GrIndexBuffer* onCreateIndexBuffer(uint32_t size,
bool dynamic) SK_OVERRIDE;
virtual GrPath* onCreatePath(const SkPath&) SK_OVERRIDE;
- virtual GrTexture* onCreatePlatformTexture(
- const GrPlatformTextureDesc& desc) SK_OVERRIDE;
- virtual GrRenderTarget* onCreatePlatformRenderTarget(
- const GrPlatformRenderTargetDesc& desc) SK_OVERRIDE;
+ virtual GrTexture* onWrapBackendTexture(const GrBackendTextureDesc&) SK_OVERRIDE;
+ virtual GrRenderTarget* onWrapBackendRenderTarget(const GrBackendRenderTargetDesc&) SK_OVERRIDE;
virtual bool createStencilBufferForRenderTarget(GrRenderTarget* rt,
int width,
int height) SK_OVERRIDE;
@@ -106,10 +104,10 @@ protected:
uint32_t numVertices) SK_OVERRIDE;
virtual void setStencilPathSettings(const GrPath&,
- GrPathFill,
+ SkPath::FillType,
GrStencilSettings* settings)
SK_OVERRIDE;
- virtual void onGpuStencilPath(const GrPath*, GrPathFill) SK_OVERRIDE;
+ virtual void onGpuStencilPath(const GrPath*, SkPath::FillType) SK_OVERRIDE;
virtual void clearStencil() SK_OVERRIDE;
virtual void clearStencilClip(const GrIRect& rect,
@@ -147,28 +145,17 @@ private:
const GrGLContextInfo& glContextInfo() const { return fGLContextInfo; }
- // adjusts texture matrix to account for orientation
- static void AdjustTextureMatrix(const GrGLTexture* texture,
- GrMatrix* matrix);
-
- // subclass may try to take advantage of identity tex matrices.
- // This helper determines if matrix will be identity after all
- // adjustments are applied.
- static bool TextureMatrixIsIdentity(const GrGLTexture* texture,
- const GrSamplerState& sampler);
-
static bool BlendCoeffReferencesConstant(GrBlendCoeff coeff);
// for readability of function impls
typedef GrGLProgram::Desc ProgramDesc;
- typedef ProgramDesc::StageDesc StageDesc;
class ProgramCache : public ::GrNoncopyable {
public:
ProgramCache(const GrGLContextInfo& gl);
void abandon();
- GrGLProgram* getProgram(const GrGLProgram::Desc& desc, const GrCustomStage** stages);
+ GrGLProgram* getProgram(const GrGLProgram::Desc& desc, const GrEffectStage* stages[]);
private:
enum {
kKeySize = sizeof(ProgramDesc),
@@ -219,9 +206,6 @@ private:
const GrTextureParams& params,
GrGLTexture* nextTexture);
- // sets the texture matrix for the currently bound program
- void flushTextureMatrix(int stage);
-
// sets the color specified by GrDrawState::setColor()
void flushColor(GrColor color);
@@ -231,14 +215,6 @@ private:
// sets the MVP matrix uniform for currently bound program
void flushViewMatrix(DrawType type);
- // flushes the parameters to two point radial gradient
- void flushRadial2(int stage);
-
- // flushes the parameters for convolution
- void flushConvolution(int stage);
-
- // flushes the color matrix
- void flushColorMatrix();
// flushes dithering, color-mask, and face culling stat
void flushMiscFixedFunctionState();
@@ -250,10 +226,9 @@ private:
void buildProgram(bool isPoints,
BlendOptFlags blendOpts,
GrBlendCoeff dstCoeff,
- const GrCustomStage** customStages,
ProgramDesc* desc);
- // Inits GrDrawTarget::Caps, sublcass may enable additional caps.
+ // Inits GrDrawTarget::Caps, subclass may enable additional caps.
void initCaps();
void initFSAASupport();
@@ -367,10 +342,10 @@ private:
} fHWAAState;
struct {
- GrMatrix fViewMatrix;
+ SkMatrix fViewMatrix;
SkISize fRTSize;
void invalidate() {
- fViewMatrix = GrMatrix::InvalidMatrix();
+ fViewMatrix = SkMatrix::InvalidMatrix();
fRTSize.fWidth = -1; // just make the first value compared illegal.
}
} fHWPathMatrixState;
diff --git a/src/gpu/gl/GrGpuGL_program.cpp b/src/gpu/gl/GrGpuGL_program.cpp
index 9e3a5d884c..5850fc4d69 100644
--- a/src/gpu/gl/GrGpuGL_program.cpp
+++ b/src/gpu/gl/GrGpuGL_program.cpp
@@ -7,8 +7,8 @@
#include "GrGpuGL.h"
-#include "GrCustomStage.h"
-#include "GrGLProgramStage.h"
+#include "GrEffect.h"
+#include "GrGLEffect.h"
#include "GrGpuVertex.h"
typedef GrGLUniformManager::UniformHandle UniformHandle;
@@ -33,7 +33,7 @@ void GrGpuGL::ProgramCache::abandon() {
}
GrGLProgram* GrGpuGL::ProgramCache::getProgram(const ProgramDesc& desc,
- const GrCustomStage** stages) {
+ const GrEffectStage* stages[]) {
Entry newEntry;
newEntry.fKey.setKeyData(desc.asKey());
@@ -89,40 +89,40 @@ void GrGpuGL::flushViewMatrix(DrawType type) {
const GrGLIRect& viewport = rt->getViewport();
viewportSize.set(viewport.fWidth, viewport.fHeight);
- const GrMatrix& vm = this->getDrawState().getViewMatrix();
+ const SkMatrix& vm = this->getDrawState().getViewMatrix();
if (kStencilPath_DrawType == type) {
if (fHWPathMatrixState.fViewMatrix != vm ||
fHWPathMatrixState.fRTSize != viewportSize) {
// rescale the coords from skia's "device" coords to GL's normalized coords,
// and perform a y-flip.
- GrMatrix m;
- m.setScale(GrIntToScalar(2) / rt->width(), GrIntToScalar(-2) / rt->height());
- m.postTranslate(-GR_Scalar1, GR_Scalar1);
+ SkMatrix m;
+ m.setScale(SkIntToScalar(2) / rt->width(), SkIntToScalar(-2) / rt->height());
+ m.postTranslate(-SK_Scalar1, SK_Scalar1);
m.preConcat(vm);
// GL wants a column-major 4x4.
GrGLfloat mv[] = {
// col 0
- GrScalarToFloat(m[GrMatrix::kMScaleX]),
- GrScalarToFloat(m[GrMatrix::kMSkewY]),
+ SkScalarToFloat(m[SkMatrix::kMScaleX]),
+ SkScalarToFloat(m[SkMatrix::kMSkewY]),
0,
- GrScalarToFloat(m[GrMatrix::kMPersp0]),
+ SkScalarToFloat(m[SkMatrix::kMPersp0]),
// col 1
- GrScalarToFloat(m[GrMatrix::kMSkewX]),
- GrScalarToFloat(m[GrMatrix::kMScaleY]),
+ SkScalarToFloat(m[SkMatrix::kMSkewX]),
+ SkScalarToFloat(m[SkMatrix::kMScaleY]),
0,
- GrScalarToFloat(m[GrMatrix::kMPersp1]),
+ SkScalarToFloat(m[SkMatrix::kMPersp1]),
// col 2
0, 0, 0, 0,
// col3
- GrScalarToFloat(m[GrMatrix::kMTransX]),
- GrScalarToFloat(m[GrMatrix::kMTransY]),
+ SkScalarToFloat(m[SkMatrix::kMTransX]),
+ SkScalarToFloat(m[SkMatrix::kMTransY]),
0.0f,
- GrScalarToFloat(m[GrMatrix::kMPersp2])
+ SkScalarToFloat(m[SkMatrix::kMPersp2])
};
GL_CALL(MatrixMode(GR_GL_PROJECTION));
GL_CALL(LoadMatrixf(mv));
@@ -131,25 +131,25 @@ void GrGpuGL::flushViewMatrix(DrawType type) {
}
} else if (!fCurrentProgram->fViewMatrix.cheapEqualTo(vm) ||
fCurrentProgram->fViewportSize != viewportSize) {
- GrMatrix m;
+ SkMatrix m;
m.setAll(
- GrIntToScalar(2) / viewportSize.fWidth, 0, -GR_Scalar1,
- 0,-GrIntToScalar(2) / viewportSize.fHeight, GR_Scalar1,
- 0, 0, GrMatrix::I()[8]);
+ SkIntToScalar(2) / viewportSize.fWidth, 0, -SK_Scalar1,
+ 0,-SkIntToScalar(2) / viewportSize.fHeight, SK_Scalar1,
+ 0, 0, SkMatrix::I()[8]);
m.setConcat(m, vm);
// ES doesn't allow you to pass true to the transpose param,
// so do our own transpose
GrGLfloat mt[] = {
- GrScalarToFloat(m[GrMatrix::kMScaleX]),
- GrScalarToFloat(m[GrMatrix::kMSkewY]),
- GrScalarToFloat(m[GrMatrix::kMPersp0]),
- GrScalarToFloat(m[GrMatrix::kMSkewX]),
- GrScalarToFloat(m[GrMatrix::kMScaleY]),
- GrScalarToFloat(m[GrMatrix::kMPersp1]),
- GrScalarToFloat(m[GrMatrix::kMTransX]),
- GrScalarToFloat(m[GrMatrix::kMTransY]),
- GrScalarToFloat(m[GrMatrix::kMPersp2])
+ SkScalarToFloat(m[SkMatrix::kMScaleX]),
+ SkScalarToFloat(m[SkMatrix::kMSkewY]),
+ SkScalarToFloat(m[SkMatrix::kMPersp0]),
+ SkScalarToFloat(m[SkMatrix::kMSkewX]),
+ SkScalarToFloat(m[SkMatrix::kMScaleY]),
+ SkScalarToFloat(m[SkMatrix::kMPersp1]),
+ SkScalarToFloat(m[SkMatrix::kMTransX]),
+ SkScalarToFloat(m[SkMatrix::kMTransY]),
+ SkScalarToFloat(m[SkMatrix::kMPersp2])
};
fCurrentProgram->fUniformManager.setMatrix3f(fCurrentProgram->fUniforms.fViewMatrixUni, mt);
fCurrentProgram->fViewMatrix = vm;
@@ -159,106 +159,6 @@ void GrGpuGL::flushViewMatrix(DrawType type) {
///////////////////////////////////////////////////////////////////////////////
-// helpers for texture matrices
-
-void GrGpuGL::AdjustTextureMatrix(const GrGLTexture* texture,
- GrMatrix* matrix) {
- GrAssert(NULL != texture);
- GrAssert(NULL != matrix);
- GrGLTexture::Orientation orientation = texture->orientation();
- if (GrGLTexture::kBottomUp_Orientation == orientation) {
- GrMatrix invY;
- invY.setAll(GR_Scalar1, 0, 0,
- 0, -GR_Scalar1, GR_Scalar1,
- 0, 0, GrMatrix::I()[8]);
- matrix->postConcat(invY);
- } else {
- GrAssert(GrGLTexture::kTopDown_Orientation == orientation);
- }
-}
-
-bool GrGpuGL::TextureMatrixIsIdentity(const GrGLTexture* texture,
- const GrSamplerState& sampler) {
- GrAssert(NULL != texture);
- if (!sampler.getMatrix().isIdentity()) {
- return false;
- }
- GrGLTexture::Orientation orientation = texture->orientation();
- if (GrGLTexture::kBottomUp_Orientation == orientation) {
- return false;
- } else {
- GrAssert(GrGLTexture::kTopDown_Orientation == orientation);
- }
- return true;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
-void GrGpuGL::flushTextureMatrix(int s) {
- const GrDrawState& drawState = this->getDrawState();
-
- // FIXME: Still assuming only a single texture per custom stage
- const GrCustomStage* stage = drawState.getSampler(s).getCustomStage();
- const GrGLTexture* texture = static_cast<const GrGLTexture*>(stage->texture(0));
- if (NULL != texture) {
-
- bool orientationChange = fCurrentProgram->fTextureOrientation[s] !=
- texture->orientation();
-
- UniformHandle matrixUni = fCurrentProgram->fUniforms.fStages[s].fTextureMatrixUni;
-
- const GrMatrix& hwMatrix = fCurrentProgram->fTextureMatrices[s];
- const GrMatrix& samplerMatrix = drawState.getSampler(s).getMatrix();
-
- if (kInvalidUniformHandle != matrixUni &&
- (orientationChange || !hwMatrix.cheapEqualTo(samplerMatrix))) {
-
- GrMatrix m = samplerMatrix;
- AdjustTextureMatrix(texture, &m);
-
- // ES doesn't allow you to pass true to the transpose param,
- // so do our own transpose
- GrGLfloat mt[] = {
- GrScalarToFloat(m[GrMatrix::kMScaleX]),
- GrScalarToFloat(m[GrMatrix::kMSkewY]),
- GrScalarToFloat(m[GrMatrix::kMPersp0]),
- GrScalarToFloat(m[GrMatrix::kMSkewX]),
- GrScalarToFloat(m[GrMatrix::kMScaleY]),
- GrScalarToFloat(m[GrMatrix::kMPersp1]),
- GrScalarToFloat(m[GrMatrix::kMTransX]),
- GrScalarToFloat(m[GrMatrix::kMTransY]),
- GrScalarToFloat(m[GrMatrix::kMPersp2])
- };
-
- fCurrentProgram->fUniformManager.setMatrix3f(matrixUni, mt);
- fCurrentProgram->fTextureMatrices[s] = samplerMatrix;
- }
-
- fCurrentProgram->fTextureOrientation[s] = texture->orientation();
- }
-}
-
-
-void GrGpuGL::flushColorMatrix() {
- UniformHandle matrixUni = fCurrentProgram->fUniforms.fColorMatrixUni;
- UniformHandle vecUni = fCurrentProgram->fUniforms.fColorMatrixVecUni;
- if (kInvalidUniformHandle != matrixUni && kInvalidUniformHandle != vecUni) {
- const float* m = this->getDrawState().getColorMatrix();
- GrGLfloat mt[] = {
- m[0], m[5], m[10], m[15],
- m[1], m[6], m[11], m[16],
- m[2], m[7], m[12], m[17],
- m[3], m[8], m[13], m[18],
- };
- static float scale = 1.0f / 255.0f;
- GrGLfloat vec[] = {
- m[4] * scale, m[9] * scale, m[14] * scale, m[19] * scale,
- };
- fCurrentProgram->fUniformManager.setMatrix4f(matrixUni, mt);
- fCurrentProgram->fUniformManager.set4fv(vecUni, 0, 1, vec);
- }
-}
-
void GrGpuGL::flushColor(GrColor color) {
const ProgramDesc& desc = fCurrentProgram->getDesc();
const GrDrawState& drawState = this->getDrawState();
@@ -366,11 +266,14 @@ bool GrGpuGL::flushGraphicsState(DrawType type) {
return false;
}
- const GrCustomStage* customStages [GrDrawState::kNumStages];
+ const GrEffectStage* stages[GrDrawState::kNumStages];
+ for (int i = 0; i < GrDrawState::kNumStages; ++i) {
+ stages[i] = drawState.isStageEnabled(i) ? &drawState.getStage(i) : NULL;
+ }
GrGLProgram::Desc desc;
- this->buildProgram(kDrawPoints_DrawType == type, blendOpts, dstCoeff, customStages, &desc);
+ this->buildProgram(kDrawPoints_DrawType == type, blendOpts, dstCoeff, &desc);
- fCurrentProgram.reset(fProgramCache->getProgram(desc, customStages));
+ fCurrentProgram.reset(fProgramCache->getProgram(desc, stages));
if (NULL == fCurrentProgram.get()) {
GrAssert(!"Failed to create program!");
return false;
@@ -399,21 +302,13 @@ bool GrGpuGL::flushGraphicsState(DrawType type) {
this->flushColor(color);
this->flushCoverage(coverage);
+ fCurrentProgram->setData(drawState);
+
for (int s = 0; s < GrDrawState::kNumStages; ++s) {
if (this->isStageEnabled(s)) {
this->flushBoundTextureAndParams(s);
-
- this->flushTextureMatrix(s);
-
- if (NULL != fCurrentProgram->fProgramStage[s]) {
- const GrSamplerState& sampler = this->getDrawState().getSampler(s);
- fCurrentProgram->fProgramStage[s]->setData(fCurrentProgram->fUniformManager,
- *sampler.getCustomStage(),
- drawState.getRenderTarget(), s);
- }
}
}
- this->flushColorMatrix();
}
this->flushStencil(type);
this->flushViewMatrix(type);
@@ -488,7 +383,7 @@ void GrGpuGL::setupGeometry(int* startVertex,
scalarType = TEXT_COORDS_GL_TYPE;
texCoordNorm = SkToBool(TEXT_COORDS_ARE_NORMALIZED);
} else {
- GR_STATIC_ASSERT(GR_SCALAR_IS_FLOAT);
+// GR_STATIC_ASSERT(SK_SCALAR_IS_FLOAT);
scalarType = GR_GL_FLOAT;
texCoordNorm = false;
}
@@ -585,30 +480,9 @@ void GrGpuGL::setupGeometry(int* startVertex,
fHWGeometryState.fArrayPtrsDirty = false;
}
-namespace {
-
-void setup_custom_stage(GrGLProgram::Desc::StageDesc* stage,
- const GrSamplerState& sampler,
- const GrGLCaps& caps,
- const GrCustomStage** customStages,
- GrGLProgram* program, int index) {
- const GrCustomStage* customStage = sampler.getCustomStage();
- if (customStage) {
- const GrProgramStageFactory& factory = customStage->getFactory();
- stage->fCustomStageKey = factory.glStageKey(*customStage, caps);
- customStages[index] = customStage;
- } else {
- stage->fCustomStageKey = 0;
- customStages[index] = NULL;
- }
-}
-
-}
-
void GrGpuGL::buildProgram(bool isPoints,
BlendOptFlags blendOpts,
GrBlendCoeff dstCoeff,
- const GrCustomStage** customStages,
ProgramDesc* desc) {
const GrDrawState& drawState = this->getDrawState();
@@ -644,8 +518,6 @@ void GrGpuGL::buildProgram(bool isPoints,
SkXfermode::kDst_Mode :
drawState.getColorFilterMode();
- desc->fColorMatrixEnabled = drawState.isStateFlagEnabled(GrDrawState::kColorMatrix_StateBit);
-
// no reason to do edge aa or look at per-vertex coverage if coverage is
// ignored
if (skipCoverage) {
@@ -681,51 +553,30 @@ void GrGpuGL::buildProgram(bool isPoints,
if (!skipCoverage && (desc->fVertexLayout &GrDrawTarget::kEdge_VertexLayoutBit)) {
desc->fVertexEdgeType = drawState.getVertexEdgeType();
+ desc->fDiscardIfOutsideEdge = drawState.getStencil().doesWrite();
} else {
- // use canonical value when not set to avoid cache misses
+ // Use canonical values when edge-aa is not enabled to avoid program cache misses.
desc->fVertexEdgeType = GrDrawState::kHairLine_EdgeType;
+ desc->fDiscardIfOutsideEdge = false;
}
for (int s = 0; s < GrDrawState::kNumStages; ++s) {
- StageDesc& stage = desc->fStages[s];
-
- stage.fOptFlags = 0;
- stage.setEnabled(this->isStageEnabled(s));
- bool skip = s < drawState.getFirstCoverageStage() ? skipColor :
- skipCoverage;
-
- if (!skip && stage.isEnabled()) {
+ bool skip = s < drawState.getFirstCoverageStage() ? skipColor : skipCoverage;
+ if (!skip && drawState.isStageEnabled(s)) {
lastEnabledStage = s;
- const GrSamplerState& sampler = drawState.getSampler(s);
- // FIXME: Still assuming one texture per custom stage
- const GrCustomStage* customStage = drawState.getSampler(s).getCustomStage();
- const GrGLTexture* texture = static_cast<const GrGLTexture*>(customStage->texture(0));
- if (NULL != texture) {
- // We call this helper function rather then simply checking the client-specified
- // texture matrix. This is because we may have to concat a y-inversion to account
- // for texture orientation.
- if (TextureMatrixIsIdentity(texture, sampler)) {
- stage.fOptFlags |= StageDesc::kIdentityMatrix_OptFlagBit;
- } else if (!sampler.getMatrix().hasPerspective()) {
- stage.fOptFlags |= StageDesc::kNoPerspective_OptFlagBit;
- }
- }
-
- setup_custom_stage(&stage, sampler, this->glCaps(), customStages,
- fCurrentProgram.get(), s);
-
+ const GrEffect* effect = drawState.getStage(s).getEffect();
+ const GrBackendEffectFactory& factory = effect->getFactory();
+ desc->fEffectKeys[s] = factory.glEffectKey(drawState.getStage(s), this->glCaps());
} else {
- stage.fOptFlags = 0;
- stage.fCustomStageKey = 0;
- customStages[s] = NULL;
+ desc->fEffectKeys[s] = 0;
}
}
desc->fDualSrcOutput = ProgramDesc::kNone_DualSrcOutput;
// Currently the experimental GS will only work with triangle prims (and it doesn't do anything
- // other than pass through values fromthe VS to the FS anyway).
+ // other than pass through values from the VS to the FS anyway).
#if 0 && GR_GL_EXPERIMENTAL_GS
desc->fExperimentalGS = this->getCaps().fGeometryShaderSupport;
#endif
diff --git a/src/gpu/gl/android/GrGLCreateNativeInterface_android.cpp b/src/gpu/gl/android/GrGLCreateNativeInterface_android.cpp
index 688f00dd2f..10792d997c 100644
--- a/src/gpu/gl/android/GrGLCreateNativeInterface_android.cpp
+++ b/src/gpu/gl/android/GrGLCreateNativeInterface_android.cpp
@@ -69,7 +69,11 @@ const GrGLInterface* GrGLCreateNativeInterface() {
interface->fPixelStorei = glPixelStorei;
interface->fReadPixels = glReadPixels;
interface->fScissor = glScissor;
+#if GR_USE_NEW_GL_SHADER_SOURCE_SIGNATURE
+ interface->fShaderSource = (GrGLShaderSourceProc) glShaderSource;
+#else
interface->fShaderSource = glShaderSource;
+#endif
interface->fStencilFunc = glStencilFunc;
interface->fStencilFuncSeparate = glStencilFuncSeparate;
interface->fStencilMask = glStencilMask;
diff --git a/src/gpu/gl/debug/GrGLCreateDebugInterface.cpp b/src/gpu/gl/debug/GrGLCreateDebugInterface.cpp
index 4aff202a9f..1af17b2f88 100644
--- a/src/gpu/gl/debug/GrGLCreateDebugInterface.cpp
+++ b/src/gpu/gl/debug/GrGLCreateDebugInterface.cpp
@@ -318,7 +318,11 @@ GrGLvoid GR_GL_FUNCTION_TYPE debugGLScissor(GrGLint x,
GrGLvoid GR_GL_FUNCTION_TYPE debugGLShaderSource(GrGLuint shader,
GrGLsizei count,
+#if GR_USE_NEW_GL_SHADER_SOURCE_SIGNATURE
+ const char* const * str,
+#else
const char** str,
+#endif
const GrGLint* length) {
}
diff --git a/src/gpu/gl/mac/GrGLCreateNativeInterface_mac.cpp b/src/gpu/gl/mac/GrGLCreateNativeInterface_mac.cpp
new file mode 100644
index 0000000000..fce96f4cc5
--- /dev/null
+++ b/src/gpu/gl/mac/GrGLCreateNativeInterface_mac.cpp
@@ -0,0 +1,280 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "gl/GrGLInterface.h"
+#include "../GrGLUtil.h"
+
+#include <OpenGL/gl.h>
+#include <OpenGL/glext.h>
+
+#include <dlfcn.h>
+
+static void* GetProcAddress(const char* name) {
+ return dlsym(RTLD_DEFAULT, name);
+}
+
+#define GET_PROC(name) (interface->f ## name = ((GrGL ## name ## Proc) GetProcAddress("gl" #name)))
+#define GET_PROC_SUFFIX(name, suffix) (interface->f ## name = ((GrGL ## name ## Proc) GetProcAddress("gl" #name #suffix)))
+
+const GrGLInterface* GrGLCreateNativeInterface() {
+ // The gl functions are not context-specific so we create one global
+ // interface
+ static SkAutoTUnref<GrGLInterface> glInterface;
+ if (!glInterface.get()) {
+ GrGLInterface* interface = new GrGLInterface;
+ glInterface.reset(interface);
+ const char* verStr = (const char*) glGetString(GL_VERSION);
+ GrGLVersion ver = GrGLGetVersionFromString(verStr);
+ const char* extStr = (const char*) glGetString(GL_EXTENSIONS);
+
+ interface->fBindingsExported = kDesktop_GrGLBinding;
+ interface->fActiveTexture = glActiveTexture;
+ interface->fAttachShader = glAttachShader;
+ interface->fBeginQuery = glBeginQuery;
+ interface->fBindAttribLocation = glBindAttribLocation;
+ interface->fBindBuffer = glBindBuffer;
+ if (ver >= GR_GL_VER(3,0)) {
+ #if GL_VERSION_3_0
+ interface->fBindFragDataLocation = glBindFragDataLocation;
+ #else
+ interface->fBindFragDataLocation = GET_PROC(BindFragDataLocation);
+ #endif
+ }
+ interface->fBindTexture = glBindTexture;
+ interface->fBlendFunc = glBlendFunc;
+
+ if (ver >= GR_GL_VER(1,4)) {
+ interface->fBlendColor = glBlendColor;
+ } else if (GrGLHasExtensionFromString("GL_ARB_imaging", extStr) ||
+ GrGLHasExtensionFromString("GL_EXT_blend_color", extStr)) {
+ GET_PROC(BlendColor);
+ }
+
+ interface->fBufferData = glBufferData;
+ interface->fBufferSubData = glBufferSubData;
+ interface->fClear = glClear;
+ interface->fClearColor = glClearColor;
+ interface->fClearStencil = glClearStencil;
+ interface->fColorMask = glColorMask;
+ interface->fCompileShader = glCompileShader;
+ interface->fCompressedTexImage2D = glCompressedTexImage2D;
+ interface->fCreateProgram = glCreateProgram;
+ interface->fCreateShader = glCreateShader;
+ interface->fCullFace = glCullFace;
+ interface->fDeleteBuffers = glDeleteBuffers;
+ interface->fDeleteProgram = glDeleteProgram;
+ interface->fDeleteQueries = glDeleteQueries;
+ interface->fDeleteShader = glDeleteShader;
+ interface->fDeleteTextures = glDeleteTextures;
+ interface->fDepthMask = glDepthMask;
+ interface->fDisable = glDisable;
+ interface->fDisableVertexAttribArray =
+ glDisableVertexAttribArray;
+ interface->fDrawArrays = glDrawArrays;
+ interface->fDrawBuffer = glDrawBuffer;
+ interface->fDrawBuffers = glDrawBuffers;
+ interface->fDrawElements = glDrawElements;
+ interface->fEnable = glEnable;
+ interface->fEnableVertexAttribArray = glEnableVertexAttribArray;
+ interface->fEndQuery = glEndQuery;
+ interface->fFinish = glFinish;
+ interface->fFlush = glFlush;
+ interface->fFrontFace = glFrontFace;
+ interface->fGenBuffers = glGenBuffers;
+ interface->fGenQueries = glGenQueries;
+ interface->fGetBufferParameteriv = glGetBufferParameteriv;
+ interface->fGetError = glGetError;
+ interface->fGetIntegerv = glGetIntegerv;
+ interface->fGetProgramInfoLog = glGetProgramInfoLog;
+ interface->fGetProgramiv = glGetProgramiv;
+ interface->fGetQueryiv = glGetQueryiv;
+ interface->fGetQueryObjectiv = glGetQueryObjectiv;
+ interface->fGetQueryObjectuiv = glGetQueryObjectuiv;
+ interface->fGetShaderInfoLog = glGetShaderInfoLog;
+ interface->fGetShaderiv = glGetShaderiv;
+ interface->fGetString = glGetString;
+ interface->fGetTexLevelParameteriv = glGetTexLevelParameteriv;
+ interface->fGenTextures = glGenTextures;
+ interface->fGetUniformLocation = glGetUniformLocation;
+ interface->fLineWidth = glLineWidth;
+ interface->fLinkProgram = glLinkProgram;
+ interface->fMapBuffer = glMapBuffer;
+ interface->fPixelStorei = glPixelStorei;
+ interface->fReadBuffer = glReadBuffer;
+ interface->fReadPixels = glReadPixels;
+ interface->fScissor = glScissor;
+ // The new OpenGLES2 header has an extra "const" in it. :(
+#if GR_USE_NEW_GL_SHADER_SOURCE_SIGNATURE
+ interface->fShaderSource = (GrGLShaderSourceProc) glShaderSource;
+#else
+ interface->fShaderSource = glShaderSource;
+#endif
+ interface->fStencilFunc = glStencilFunc;
+ interface->fStencilFuncSeparate = glStencilFuncSeparate;
+ interface->fStencilMask = glStencilMask;
+ interface->fStencilMaskSeparate = glStencilMaskSeparate;
+ interface->fStencilOp = glStencilOp;
+ interface->fStencilOpSeparate = glStencilOpSeparate;
+ // mac uses GLenum for internalFormat param (non-standard)
+ // amounts to int vs. uint.
+ interface->fTexImage2D = (GrGLTexImage2DProc)glTexImage2D;
+ interface->fTexParameteri = glTexParameteri;
+ interface->fTexParameteriv = glTexParameteriv;
+ #if GL_ARB_texture_storage || GL_VERSION_4_2
+ interface->fTexStorage2D = glTexStorage2D
+ #elif GL_EXT_texture_storage
+ interface->fTexStorage2D = glTexStorage2DEXT;
+ #else
+ if (ver >= GR_GL_VER(4,2) ||
+ GrGLHasExtensionFromString("GL_ARB_texture_storage", extStr)) {
+ GET_PROC(TexStorage2D);
+ } else if (GrGLHasExtensionFromString("GL_EXT_texture_storage", extStr)) {
+ GET_PROC_SUFFIX(TexStorage2D, EXT);
+ }
+ #endif
+ interface->fTexSubImage2D = glTexSubImage2D;
+ interface->fUniform1f = glUniform1f;
+ interface->fUniform1i = glUniform1i;
+ interface->fUniform1fv = glUniform1fv;
+ interface->fUniform1iv = glUniform1iv;
+ interface->fUniform2f = glUniform2f;
+ interface->fUniform2i = glUniform2i;
+ interface->fUniform2fv = glUniform2fv;
+ interface->fUniform2iv = glUniform2iv;
+ interface->fUniform3f = glUniform3f;
+ interface->fUniform3i = glUniform3i;
+ interface->fUniform3fv = glUniform3fv;
+ interface->fUniform3iv = glUniform3iv;
+ interface->fUniform4f = glUniform4f;
+ interface->fUniform4i = glUniform4i;
+ interface->fUniform4fv = glUniform4fv;
+ interface->fUniform4iv = glUniform4iv;
+ interface->fUniform4fv = glUniform4fv;
+ interface->fUniformMatrix2fv = glUniformMatrix2fv;
+ interface->fUniformMatrix3fv = glUniformMatrix3fv;
+ interface->fUniformMatrix4fv = glUniformMatrix4fv;
+ interface->fUnmapBuffer = glUnmapBuffer;
+ interface->fUseProgram = glUseProgram;
+ interface->fVertexAttrib4fv = glVertexAttrib4fv;
+ interface->fVertexAttribPointer = glVertexAttribPointer;
+ interface->fViewport = glViewport;
+
+ if (ver >= GR_GL_VER(3,3) || GrGLHasExtensionFromString("GL_ARB_timer_query", extStr)) {
+ // ARB extension doesn't use the ARB suffix on the function name
+ #if GL_ARB_timer_query || GL_VERSION_3_3
+ interface->fQueryCounter = glQueryCounter;
+ interface->fGetQueryObjecti64v = glGetQueryObjecti64v;
+ interface->fGetQueryObjectui64v = glGetQueryObjectui64v;
+ #else
+ interface->fQueryCounter = GET_PROC(QueryCounter);
+ interface->fGetQueryObjecti64v = GET_PROC(GetQueryObjecti64v);
+ interface->fGetQueryObjectui64v = GET_PROC(GetQueryObjectui64v);
+ #endif
+ } else if (GrGLHasExtensionFromString("GL_EXT_timer_query", extStr)) {
+ #if GL_EXT_timer_query
+ interface->fGetQueryObjecti64v = glGetQueryObjecti64vEXT;
+ interface->fGetQueryObjectui64v = glGetQueryObjectui64vEXT;
+ #else
+ interface->fGetQueryObjecti64v = GET_PROC_SUFFIX(GetQueryObjecti64v, EXT);
+ interface->fGetQueryObjectui64v = GET_PROC_SUFFIX(GetQueryObjectui64v, EXT);
+ #endif
+ }
+
+ if (ver >= GR_GL_VER(3,0) || GrGLHasExtensionFromString("GL_ARB_framebuffer_object", extStr)) {
+ // ARB extension doesn't use the ARB suffix on the function names
+ #if GL_VERSION_3_0 || GL_ARB_framebuffer_object
+ interface->fGenFramebuffers = glGenFramebuffers;
+ interface->fGetFramebufferAttachmentParameteriv = glGetFramebufferAttachmentParameteriv;
+ interface->fGetRenderbufferParameteriv = glGetRenderbufferParameteriv;
+ interface->fBindFramebuffer = glBindFramebuffer;
+ interface->fFramebufferTexture2D = glFramebufferTexture2D;
+ interface->fCheckFramebufferStatus = glCheckFramebufferStatus;
+ interface->fDeleteFramebuffers = glDeleteFramebuffers;
+ interface->fRenderbufferStorage = glRenderbufferStorage;
+ interface->fGenRenderbuffers = glGenRenderbuffers;
+ interface->fDeleteRenderbuffers = glDeleteRenderbuffers;
+ interface->fFramebufferRenderbuffer = glFramebufferRenderbuffer;
+ interface->fBindRenderbuffer = glBindRenderbuffer;
+ interface->fRenderbufferStorageMultisample = glRenderbufferStorageMultisample;
+ interface->fBlitFramebuffer = glBlitFramebuffer;
+ #else
+ interface->fGenFramebuffers = GET_PROC(GenFramebuffers);
+ interface->fGetFramebufferAttachmentParameteriv = GET_PROC(GetFramebufferAttachmentParameteriv);
+ interface->fGetRenderbufferParameteriv = GET_PROC(GetRenderbufferParameteriv);
+ interface->fBindFramebuffer = GET_PROC(BindFramebuffer);
+ interface->fFramebufferTexture2D = GET_PROC(FramebufferTexture2D);
+ interface->fCheckFramebufferStatus = GET_PROC(CheckFramebufferStatus);
+ interface->fDeleteFramebuffers = GET_PROC(DeleteFramebuffers);
+ interface->fRenderbufferStorage = GET_PROC(RenderbufferStorage);
+ interface->fGenRenderbuffers = GET_PROC(GenRenderbuffers);
+ interface->fDeleteRenderbuffers = GET_PROC(DeleteRenderbuffers);
+ interface->fFramebufferRenderbuffer = GET_PROC(FramebufferRenderbuffer);
+ interface->fBindRenderbuffer = GET_PROC(BindRenderbuffer);
+ interface->fRenderbufferStorageMultisample = GET_PROC(RenderbufferStorageMultisample);
+ interface->fBlitFramebuffer = GET_PROC(BlitFramebuffer);
+ #endif
+ } else {
+ if (GrGLHasExtensionFromString("GL_EXT_framebuffer_object", extStr)) {
+ #if GL_EXT_framebuffer_object
+ interface->fGenFramebuffers = glGenFramebuffersEXT;
+ interface->fGetFramebufferAttachmentParameteriv = glGetFramebufferAttachmentParameterivEXT;
+ interface->fGetRenderbufferParameteriv = glGetRenderbufferParameterivEXT;
+ interface->fBindFramebuffer = glBindFramebufferEXT;
+ interface->fFramebufferTexture2D = glFramebufferTexture2DEXT;
+ interface->fCheckFramebufferStatus = glCheckFramebufferStatusEXT;
+ interface->fDeleteFramebuffers = glDeleteFramebuffersEXT;
+ interface->fRenderbufferStorage = glRenderbufferStorageEXT;
+ interface->fGenRenderbuffers = glGenRenderbuffersEXT;
+ interface->fDeleteRenderbuffers = glDeleteRenderbuffersEXT;
+ interface->fFramebufferRenderbuffer = glFramebufferRenderbufferEXT;
+ interface->fBindRenderbuffer = glBindRenderbufferEXT;
+ #else
+ interface->fGenFramebuffers = GET_PROC_SUFFIX(GenFramebuffers, EXT);
+ interface->fGetFramebufferAttachmentParameteriv = GET_PROC_SUFFIX(GetFramebufferAttachmentParameteriv, EXT);
+ interface->fGetRenderbufferParameteriv = GET_PROC_SUFFIX(GetRenderbufferParameteriv, EXT);
+ interface->fBindFramebuffer = GET_PROC_SUFFIX(BindFramebuffer, EXT);
+ interface->fFramebufferTexture2D = GET_PROC_SUFFIX(FramebufferTexture2D, EXT);
+ interface->fCheckFramebufferStatus = GET_PROC_SUFFIX(CheckFramebufferStatus, EXT);
+ interface->fDeleteFramebuffers = GET_PROC_SUFFIX(DeleteFramebuffers, EXT);
+ interface->fRenderbufferStorage = GET_PROC_SUFFIX(RenderbufferStorage, EXT);
+ interface->fGenRenderbuffers = GET_PROC_SUFFIX(GenRenderbuffers, EXT);
+ interface->fDeleteRenderbuffers = GET_PROC_SUFFIX(DeleteRenderbuffers, EXT);
+ interface->fFramebufferRenderbuffer = GET_PROC_SUFFIX(FramebufferRenderbuffer, EXT);
+ interface->fBindRenderbuffer = GET_PROC_SUFFIX(BindRenderbuffer, EXT);
+ #endif
+ }
+ if (GrGLHasExtensionFromString("GL_EXT_framebuffer_multisample", extStr)) {
+ #if GL_EXT_framebuffer_multisample
+ interface->fRenderbufferStorageMultisample = glRenderbufferStorageMultisampleEXT;
+ #else
+ interface->fRenderbufferStorageMultisample = GET_PROC_SUFFIX(RenderbufferStorageMultisample, EXT);
+ #endif
+ }
+ if (GrGLHasExtensionFromString("", extStr)) {
+ #if GL_EXT_framebuffer_blit
+ interface->fBlitFramebuffer = glBlitFramebufferEXT;
+ #else
+ interface->fBlitFramebuffer = GET_PROC_SUFFIX(BlitFramebuffer, EXT);
+ #endif
+ }
+ }
+ if (ver >= GR_GL_VER(3,3) || GrGLHasExtensionFromString("GL_ARB_blend_func_extended", extStr)) {
+ // ARB extension doesn't use the ARB suffix on the function name
+ #if GL_VERSION_3_3 || GL_ARB_blend_func_extended
+ interface->fBindFragDataLocationIndexed = glBindFragDataLocationIndexed;
+ #else
+ interface->fBindFragDataLocationIndexed = GET_PROC(BindFragDataLocationIndexed);
+ #endif
+ }
+
+ interface->fBindingsExported = kDesktop_GrGLBinding;
+ }
+ glInterface.get()->ref();
+ return glInterface.get();
+}
diff --git a/src/gpu/gl/nacl/SkNativeGLContext_nacl.cpp b/src/gpu/gl/nacl/SkNativeGLContext_nacl.cpp
new file mode 100644
index 0000000000..59ed2bf86a
--- /dev/null
+++ b/src/gpu/gl/nacl/SkNativeGLContext_nacl.cpp
@@ -0,0 +1,34 @@
+
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "gl/SkNativeGLContext.h"
+
+SkNativeGLContext::AutoContextRestore::AutoContextRestore() {
+}
+
+SkNativeGLContext::AutoContextRestore::~AutoContextRestore() {
+}
+
+SkNativeGLContext::SkNativeGLContext()
+ : fContext(NULL)
+ , fDisplay(NULL)
+{
+}
+
+SkNativeGLContext::~SkNativeGLContext() {
+ this->destroyGLContext();
+}
+
+void SkNativeGLContext::destroyGLContext() {
+}
+
+const GrGLInterface* SkNativeGLContext::createGLContext() {
+ return NULL;
+}
+
+void SkNativeGLContext::makeCurrent() const {
+}
diff --git a/src/gpu/gr_unittests.cpp b/src/gpu/gr_unittests.cpp
index a0f0c183e1..1d7653b71a 100644
--- a/src/gpu/gr_unittests.cpp
+++ b/src/gpu/gr_unittests.cpp
@@ -10,7 +10,7 @@
#include "GrBinHashKey.h"
#include "GrDrawTarget.h"
-#include "GrMatrix.h"
+#include "SkMatrix.h"
#include "GrRedBlackTree.h"
#include "GrTDArray.h"
diff --git a/src/image/SkImagePriv.cpp b/src/image/SkImagePriv.cpp
index e7a244ab54..e10a44c28c 100644
--- a/src/image/SkImagePriv.cpp
+++ b/src/image/SkImagePriv.cpp
@@ -108,8 +108,7 @@ SkImage* SkNewImageFromBitmap(const SkBitmap& bm, bool canSharePixelRef) {
} else {
bm.lockPixels();
if (bm.getPixels()) {
- image = SkImage::NewRasterCopy(info, NULL, bm.getPixels(),
- bm.rowBytes());
+ image = SkImage::NewRasterCopy(info, bm.getPixels(), bm.rowBytes());
}
bm.unlockPixels();
}
diff --git a/src/image/SkImagePriv.h b/src/image/SkImagePriv.h
index 9332abc7af..3210cae031 100644
--- a/src/image/SkImagePriv.h
+++ b/src/image/SkImagePriv.h
@@ -55,4 +55,14 @@ static inline size_t SkImageMinRowBytes(const SkImage::Info& info) {
// in which case the surface may need to perform a copy-on-write.
extern SkPixelRef* SkBitmapImageGetPixelRef(SkImage* rasterImage);
+// Given an image created with NewTexture, return its GrTexture. This
+// may be called to see if the surface and the image share the same GrTexture,
+// in which case the surface may need to perform a copy-on-write.
+extern GrTexture* SkTextureImageGetTexture(SkImage* rasterImage);
+
+// Update the texture wrapped by an image created with NewTexture. This
+// is called when a surface and image share the same GrTexture and the
+// surface needs to perform a copy-on-write
+extern void SkTextureImageSetTexture(SkImage* image, GrTexture* texture);
+
#endif
diff --git a/src/image/SkImage_Base.h b/src/image/SkImage_Base.h
index ed74610493..648b25914f 100644
--- a/src/image/SkImage_Base.h
+++ b/src/image/SkImage_Base.h
@@ -14,7 +14,7 @@ class SkImage_Base : public SkImage {
public:
SkImage_Base(int width, int height) : INHERITED(width, height) {}
- virtual void onDraw(SkCanvas*, SkScalar, SkScalar, const SkPaint*) = 0;
+ virtual void onDraw(SkCanvas*, SkScalar x, SkScalar y, const SkPaint*) = 0;
private:
typedef SkImage INHERITED;
diff --git a/src/image/SkImage_Gpu.cpp b/src/image/SkImage_Gpu.cpp
index e061e2d737..c721077b62 100644
--- a/src/image/SkImage_Gpu.cpp
+++ b/src/image/SkImage_Gpu.cpp
@@ -9,65 +9,77 @@
#include "SkImagePriv.h"
#include "SkBitmap.h"
#include "SkCanvas.h"
-#include "SkData.h"
-#include "SkDataPixelRef.h"
+#include "GrContext.h"
+#include "GrTexture.h"
+#include "SkGrPixelRef.h"
class SkImage_Gpu : public SkImage_Base {
public:
- static bool ValidArgs(GrContext* context,
- const GrPlatformTextureDesc& desc) {
- if (0 == desc.fTextureHandle) {
- return false;
- }
- if (desc.fWidth < 0 || desc.fHeight < 0) {
- return false;
- }
- return true;
- }
+ SK_DECLARE_INST_COUNT(SkImage_Gpu)
- SkImage_Gpu(GrContext* context, const GrPlatformTextureDesc& desc);
+ SkImage_Gpu(GrTexture*);
virtual ~SkImage_Gpu();
- virtual void onDraw(SkCanvas*, SkScalar, SkScalar, const SkPaint*) SK_OVERRIDE;
+ virtual void onDraw(SkCanvas*, SkScalar x, SkScalar y, const SkPaint*) SK_OVERRIDE;
+
+ GrTexture* getTexture() { return fTexture; }
+
+ void setTexture(GrTexture* texture);
private:
+ GrTexture* fTexture;
SkBitmap fBitmap;
typedef SkImage_Base INHERITED;
};
+SK_DEFINE_INST_COUNT(SkImage_Gpu)
+
///////////////////////////////////////////////////////////////////////////////
-SkImage_Gpu::SkImage_Gpu(GrContext* context, const GrPlatformTextureDesc& desc)
- : INHERITED(desc.fWidth, desc.fHeight) {
-#if 0
- bool isOpaque;
- SkBitmap::Config config = SkImageInfoToBitmapConfig(info, &isOpaque);
-
- fBitmap.setConfig(config, info.fWidth, info.fHeight, rowBytes);
- fBitmap.setPixelRef(SkNEW_ARGS(SkDataPixelRef, (data)))->unref();
- fBitmap.setIsOpaque(isOpaque);
- fBitmap.setImmutable();
-#endif
+SkImage_Gpu::SkImage_Gpu(GrTexture* texture)
+ : INHERITED(texture->width(), texture->height())
+ , fTexture(texture) {
+
+ SkASSERT(NULL != fTexture);
+ fTexture->ref();
+ fBitmap.setConfig(SkBitmap::kARGB_8888_Config, fTexture->width(), fTexture->height());
+ fBitmap.setPixelRef(new SkGrPixelRef(fTexture))->unref();
}
-SkImage_Gpu::~SkImage_Gpu() {}
+SkImage_Gpu::~SkImage_Gpu() {
+ SkSafeUnref(fTexture);
+}
-void SkImage_Gpu::onDraw(SkCanvas* canvas, SkScalar x, SkScalar y, const SkPaint* paint) {
+void SkImage_Gpu::onDraw(SkCanvas* canvas, SkScalar x, SkScalar y,
+ const SkPaint* paint) {
canvas->drawBitmap(fBitmap, x, y, paint);
}
-///////////////////////////////////////////////////////////////////////////////
+void SkImage_Gpu::setTexture(GrTexture* texture) {
-SkImage* SkImage::NewRasterCopy(NewTexture(GrContext* context,
- const GrPlatformTextureDesc& desc) {
- if (NULL == context) {
- return NULL;
+ if (texture == fTexture) {
+ return;
}
- if (!SkImage_Gpu::ValidArgs(context, desc)) {
+
+ SkRefCnt_SafeAssign(fTexture, texture);
+ fBitmap.setPixelRef(new SkGrPixelRef(texture))->unref();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkImage* SkImage::NewTexture(GrTexture* texture) {
+ if (NULL == texture) {
return NULL;
}
- return SkNEW_ARGS(SkImage_Gpu, (context, desc));
+ return SkNEW_ARGS(SkImage_Gpu, (texture));
+}
+
+GrTexture* SkTextureImageGetTexture(SkImage* image) {
+ return ((SkImage_Gpu*)image)->getTexture();
}
+void SkTextureImageSetTexture(SkImage* image, GrTexture* texture) {
+ ((SkImage_Gpu*)image)->setTexture(texture);
+}
diff --git a/src/image/SkImage_Raster.cpp b/src/image/SkImage_Raster.cpp
index c9a132d371..8638052fc5 100644
--- a/src/image/SkImage_Raster.cpp
+++ b/src/image/SkImage_Raster.cpp
@@ -14,7 +14,7 @@
class SkImage_Raster : public SkImage_Base {
public:
- static bool ValidArgs(const Info& info, SkColorSpace* cs, size_t rowBytes) {
+ static bool ValidArgs(const Info& info, size_t rowBytes) {
const int maxDimension = SK_MaxS32 >> 2;
const size_t kMaxPixelByteSize = SK_MaxS32;
@@ -51,7 +51,7 @@ public:
static SkImage* NewEmpty();
- SkImage_Raster(const SkImage::Info&, SkColorSpace*, SkData*, size_t rb);
+ SkImage_Raster(const SkImage::Info&, SkData*, size_t rb);
virtual ~SkImage_Raster();
virtual void onDraw(SkCanvas*, SkScalar, SkScalar, const SkPaint*) SK_OVERRIDE;
@@ -81,9 +81,8 @@ SkImage* SkImage_Raster::NewEmpty() {
return gEmpty;
}
-SkImage_Raster::SkImage_Raster(const Info& info, SkColorSpace* cs,
- SkData* data, size_t rowBytes)
-: INHERITED(info.fWidth, info.fHeight) {
+SkImage_Raster::SkImage_Raster(const Info& info, SkData* data, size_t rowBytes)
+ : INHERITED(info.fWidth, info.fHeight) {
bool isOpaque;
SkBitmap::Config config = SkImageInfoToBitmapConfig(info, &isOpaque);
@@ -112,9 +111,8 @@ void SkImage_Raster::onDraw(SkCanvas* canvas, SkScalar x, SkScalar y, const SkPa
///////////////////////////////////////////////////////////////////////////////
-SkImage* SkImage::NewRasterCopy(const SkImage::Info& info, SkColorSpace* cs,
- const void* pixels, size_t rowBytes) {
- if (!SkImage_Raster::ValidArgs(info, cs, rowBytes)) {
+SkImage* SkImage::NewRasterCopy(const SkImage::Info& info, const void* pixels, size_t rowBytes) {
+ if (!SkImage_Raster::ValidArgs(info, rowBytes)) {
return NULL;
}
if (0 == info.fWidth && 0 == info.fHeight) {
@@ -127,13 +125,12 @@ SkImage* SkImage::NewRasterCopy(const SkImage::Info& info, SkColorSpace* cs,
// Here we actually make a copy of the caller's pixel data
SkAutoDataUnref data(SkData::NewWithCopy(pixels, info.fHeight * rowBytes));
- return SkNEW_ARGS(SkImage_Raster, (info, cs, data, rowBytes));
+ return SkNEW_ARGS(SkImage_Raster, (info, data, rowBytes));
}
-SkImage* SkImage::NewRasterData(const SkImage::Info& info, SkColorSpace* cs,
- SkData* pixelData, size_t rowBytes) {
- if (!SkImage_Raster::ValidArgs(info, cs, rowBytes)) {
+SkImage* SkImage::NewRasterData(const SkImage::Info& info, SkData* pixelData, size_t rowBytes) {
+ if (!SkImage_Raster::ValidArgs(info, rowBytes)) {
return NULL;
}
if (0 == info.fWidth && 0 == info.fHeight) {
@@ -151,7 +148,7 @@ SkImage* SkImage::NewRasterData(const SkImage::Info& info, SkColorSpace* cs,
}
SkAutoDataUnref data(pixelData);
- return SkNEW_ARGS(SkImage_Raster, (info, cs, data, rowBytes));
+ return SkNEW_ARGS(SkImage_Raster, (info, data, rowBytes));
}
SkImage* SkNewImageFromPixelRef(const SkImage::Info& info, SkPixelRef* pr,
diff --git a/src/image/SkSurface.cpp b/src/image/SkSurface.cpp
index 2860a5863d..6547780438 100644
--- a/src/image/SkSurface.cpp
+++ b/src/image/SkSurface.cpp
@@ -43,8 +43,6 @@ void SkSurface_Base::onDraw(SkCanvas* canvas, SkScalar x, SkScalar y,
}
}
-void SkSurface_Base::onCopyOnWrite(SkImage*, SkCanvas*) {}
-
SkCanvas* SkSurface_Base::getCachedCanvas() {
if (NULL == fCachedCanvas) {
fCachedCanvas = this->onNewCanvas();
@@ -125,8 +123,8 @@ SkImage* SkSurface::newImageShapshot() {
return image;
}
-SkSurface* SkSurface::newSurface(const SkImage::Info& info, SkColorSpace* cs) {
- return asSB(this)->onNewSurface(info, cs);
+SkSurface* SkSurface::newSurface(const SkImage::Info& info) {
+ return asSB(this)->onNewSurface(info);
}
void SkSurface::draw(SkCanvas* canvas, SkScalar x, SkScalar y,
diff --git a/src/image/SkSurface_Base.h b/src/image/SkSurface_Base.h
index 52b494f237..e5ca498b85 100644
--- a/src/image/SkSurface_Base.h
+++ b/src/image/SkSurface_Base.h
@@ -23,7 +23,7 @@ public:
*/
virtual SkCanvas* onNewCanvas() = 0;
- virtual SkSurface* onNewSurface(const SkImage::Info&, SkColorSpace*) = 0;
+ virtual SkSurface* onNewSurface(const SkImage::Info&) = 0;
/**
* Allocate an SkImage that represents the current contents of the surface.
@@ -51,7 +51,7 @@ public:
*
* The default implementation does nothing.
*/
- virtual void onCopyOnWrite(SkImage* cachedImage, SkCanvas*);
+ virtual void onCopyOnWrite(SkImage* cachedImage, SkCanvas*) = 0;
inline SkCanvas* getCachedCanvas();
inline SkImage* getCachedImage();
diff --git a/src/image/SkSurface_Gpu.cpp b/src/image/SkSurface_Gpu.cpp
index d707d93423..74fbe0b0b2 100644
--- a/src/image/SkSurface_Gpu.cpp
+++ b/src/image/SkSurface_Gpu.cpp
@@ -8,125 +8,107 @@
#include "SkSurface_Base.h"
#include "SkImagePriv.h"
#include "SkCanvas.h"
-#include "SkMallocPixelRef.h"
-
-static const size_t kIgnoreRowBytesValue = (size_t)~0;
+#include "SkGpuDevice.h"
class SkSurface_Gpu : public SkSurface_Base {
public:
- static bool Valid(const SkImage::Info&, SkColorSpace*, size_t rb = kIgnoreRowBytesValue);
+ SK_DECLARE_INST_COUNT(SkSurface_Gpu)
- SkSurface_Gpu(const SkImage::Info&, SkColorSpace*, void*, size_t rb);
- SkSurface_Gpu(const SkImage::Info&, SkColorSpace*, SkPixelRef*, size_t rb);
+ SkSurface_Gpu(GrContext*, const SkImage::Info&, int sampleCount);
+ SkSurface_Gpu(GrContext*, GrRenderTarget*);
+ virtual ~SkSurface_Gpu();
virtual SkCanvas* onNewCanvas() SK_OVERRIDE;
- virtual SkSurface* onNewSurface(const SkImage::Info&, SkColorSpace*) SK_OVERRIDE;
+ virtual SkSurface* onNewSurface(const SkImage::Info&) SK_OVERRIDE;
virtual SkImage* onNewImageShapshot() SK_OVERRIDE;
virtual void onDraw(SkCanvas*, SkScalar x, SkScalar y,
const SkPaint*) SK_OVERRIDE;
+ virtual void onCopyOnWrite(SkImage*, SkCanvas*) SK_OVERRIDE;
private:
- SkBitmap fBitmap;
- bool fWeOwnThePixels;
+ SkGpuDevice* fDevice;
typedef SkSurface_Base INHERITED;
};
-///////////////////////////////////////////////////////////////////////////////
+SK_DEFINE_INST_COUNT(SkSurface_Gpu)
-bool SkSurface_Gpu::Valid(const SkImage::Info& info, SkColorSpace* cs,
- size_t rowBytes) {
- static const size_t kMaxTotalSize = SK_MaxS32;
+///////////////////////////////////////////////////////////////////////////////
+SkSurface_Gpu::SkSurface_Gpu(GrContext* ctx, const SkImage::Info& info,
+ int sampleCount)
+ : INHERITED(info.fWidth, info.fHeight) {
bool isOpaque;
SkBitmap::Config config = SkImageInfoToBitmapConfig(info, &isOpaque);
- int shift = 0;
- switch (config) {
- case SkBitmap::kA8_Config:
- shift = 0;
- break;
- case SkBitmap::kRGB_565_Config:
- shift = 1;
- break;
- case SkBitmap::kARGB_8888_Config:
- shift = 2;
- break;
- default:
- return false;
- }
+ fDevice = SkNEW_ARGS(SkGpuDevice, (ctx, config, info.fWidth, info.fHeight, sampleCount));
- // TODO: examine colorspace
-
- if (kIgnoreRowBytesValue == rowBytes) {
- return true;
- }
-
- uint64_t minRB = (uint64_t)info.fWidth << shift;
- if (minRB > rowBytes) {
- return false;
+ if (!isOpaque) {
+ fDevice->clear(0x0);
}
+}
- size_t alignedRowBytes = rowBytes >> shift << shift;
- if (alignedRowBytes != rowBytes) {
- return false;
- }
+SkSurface_Gpu::SkSurface_Gpu(GrContext* ctx, GrRenderTarget* renderTarget)
+ : INHERITED(renderTarget->width(), renderTarget->height()) {
+ fDevice = SkNEW_ARGS(SkGpuDevice, (ctx, renderTarget));
- uint64_t size = (uint64_t)info.fHeight * rowBytes;
- if (size > kMaxTotalSize) {
- return false;
+ if (kRGB_565_GrPixelConfig != renderTarget->config()) {
+ fDevice->clear(0x0);
}
-
- return true;
}
-SkSurface_Gpu::SkSurface_Gpu(const SkImage::Info& info, SkColorSpace* cs,
- void* pixels, size_t rb)
- : INHERITED(info.fWidth, info.fHeight) {
- bool isOpaque;
- SkBitmap::Config config = SkImageInfoToBitmapConfig(info, &isOpaque);
-
- fBitmap.setConfig(config, info.fWidth, info.fHeight, rb);
- fBitmap.setPixels(pixels);
- fBitmap.setIsOpaque(isOpaque);
- fWeOwnThePixels = false;
-}
-
-SkSurface_Gpu::SkSurface_Gpu(const SkImage::Info& info, SkColorSpace* cs,
- SkPixelRef* pr, size_t rb)
- : INHERITED(info.fWidth, info.fHeight) {
- bool isOpaque;
- SkBitmap::Config config = SkImageInfoToBitmapConfig(info, &isOpaque);
-
- fBitmap.setConfig(config, info.fWidth, info.fHeight, rb);
- fBitmap.setPixelRef(pr);
- fBitmap.setIsOpaque(isOpaque);
- fWeOwnThePixels = true;
-
- if (!isOpaque) {
- fBitmap.eraseColor(0);
- }
+SkSurface_Gpu::~SkSurface_Gpu() {
+ SkSafeUnref(fDevice);
}
SkCanvas* SkSurface_Gpu::onNewCanvas() {
- return SkNEW_ARGS(SkCanvas, (fBitmap));
+ return SkNEW_ARGS(SkCanvas, (fDevice));
}
-SkSurface* SkSurface_Gpu::onNewSurface(const SkImage::Info& info,
- SkColorSpace* cs) {
- return SkSurface::NewRaster(info, cs);
+SkSurface* SkSurface_Gpu::onNewSurface(const SkImage::Info& info) {
+ GrRenderTarget* rt = (GrRenderTarget*) fDevice->accessRenderTarget();
+ int sampleCount = rt->numSamples();
+ return SkSurface::NewRenderTarget(fDevice->context(), info, sampleCount);
}
SkImage* SkSurface_Gpu::onNewImageShapshot() {
- // if we don't own the pixels, we need to make a deep-copy
- // if we do, we need to perform a copy-on-write the next time
- // we draw to this bitmap from our canvas...
- return SkNewImageFromBitmap(fBitmap);
+
+ GrRenderTarget* rt = (GrRenderTarget*) fDevice->accessRenderTarget();
+
+ return SkImage::NewTexture(rt->asTexture());
}
void SkSurface_Gpu::onDraw(SkCanvas* canvas, SkScalar x, SkScalar y,
const SkPaint* paint) {
- canvas->drawBitmap(fBitmap, x, y, paint);
+ canvas->drawBitmap(fDevice->accessBitmap(false), x, y, paint);
+}
+
+// Copy the contents of the SkGpuDevice into a new texture and give that
+// texture to the SkImage. Note that this flushes the SkGpuDevice but
+// doesn't force an OpenGL flush.
+void SkSurface_Gpu::onCopyOnWrite(SkImage* image, SkCanvas* canvas) {
+ GrRenderTarget* rt = (GrRenderTarget*) fDevice->accessRenderTarget();
+
+ // are we sharing our render target with the image?
+ if (rt->asTexture() == SkTextureImageGetTexture(image)) {
+ GrTextureDesc desc;
+ // copyTexture requires a render target as the destination
+ desc.fFlags = kRenderTarget_GrTextureFlagBit;
+ desc.fWidth = fDevice->width();
+ desc.fHeight = fDevice->height();
+ desc.fConfig = SkBitmapConfig2GrPixelConfig(fDevice->config());
+ desc.fSampleCnt = 0;
+
+ SkAutoTUnref<GrTexture> tex(fDevice->context()->createUncachedTexture(desc, NULL, 0));
+ if (NULL == tex) {
+ SkTextureImageSetTexture(image, NULL);
+ return;
+ }
+
+ fDevice->context()->copyTexture(rt->asTexture(), tex->asRenderTarget());
+
+ SkTextureImageSetTexture(image, tex);
+ }
}
///////////////////////////////////////////////////////////////////////////////
@@ -140,15 +122,26 @@ SkSurface* SkSurface::NewRenderTargetDirect(GrContext* ctx,
return SkNEW_ARGS(SkSurface_Gpu, (ctx, target));
}
-SkSurface* SkSurface::NewRenderTarget(GrContext* ctx, const SkImage::Info& info,
- SkColorSpace*, int sampleCount) {
+SkSurface* SkSurface::NewRenderTarget(GrContext* ctx, const SkImage::Info& info, int sampleCount) {
if (NULL == ctx) {
return NULL;
}
- if (!SkSurface_Gpu::Valid(info, cs, sampleCount)) {
+
+ bool isOpaque;
+ SkBitmap::Config config = SkImageInfoToBitmapConfig(info, &isOpaque);
+
+ GrTextureDesc desc;
+ desc.fFlags = kRenderTarget_GrTextureFlagBit;
+ desc.fWidth = info.fWidth;
+ desc.fHeight = info.fHeight;
+ desc.fConfig = SkBitmapConfig2GrPixelConfig(config);
+ desc.fSampleCnt = sampleCount;
+
+ SkAutoTUnref<GrTexture> tex(ctx->createUncachedTexture(desc, NULL, 0));
+ if (NULL == tex) {
return NULL;
}
-// return SkNEW_ARGS(SkSurface_Gpu, (info, cs, pr, rowBytes));
+ return SkNEW_ARGS(SkSurface_Gpu, (ctx, tex->asRenderTarget()));
}
diff --git a/src/image/SkSurface_Picture.cpp b/src/image/SkSurface_Picture.cpp
index bac09c59a0..54998445bc 100644
--- a/src/image/SkSurface_Picture.cpp
+++ b/src/image/SkSurface_Picture.cpp
@@ -20,10 +20,11 @@ public:
virtual ~SkSurface_Picture();
virtual SkCanvas* onNewCanvas() SK_OVERRIDE;
- virtual SkSurface* onNewSurface(const SkImage::Info&, SkColorSpace*) SK_OVERRIDE;
+ virtual SkSurface* onNewSurface(const SkImage::Info&) SK_OVERRIDE;
virtual SkImage* onNewImageShapshot() SK_OVERRIDE;
virtual void onDraw(SkCanvas*, SkScalar x, SkScalar y,
const SkPaint*) SK_OVERRIDE;
+ virtual void onCopyOnWrite(SkImage*, SkCanvas*) SK_OVERRIDE;
private:
SkPicture* fPicture;
@@ -51,7 +52,7 @@ SkCanvas* SkSurface_Picture::onNewCanvas() {
return canvas;
}
-SkSurface* SkSurface_Picture::onNewSurface(const SkImage::Info& info, SkColorSpace*) {
+SkSurface* SkSurface_Picture::onNewSurface(const SkImage::Info& info) {
return SkSurface::NewPicture(info.fWidth, info.fHeight);
}
@@ -63,7 +64,7 @@ SkImage* SkSurface_Picture::onNewImageShapshot() {
info.fWidth = info.fHeight = 0;
info.fColorType = SkImage::kPMColor_ColorType;
info.fAlphaType = SkImage::kOpaque_AlphaType;
- return SkImage::NewRasterCopy(info, NULL, NULL, 0);
+ return SkImage::NewRasterCopy(info, NULL, 0);
}
}
@@ -75,6 +76,11 @@ void SkSurface_Picture::onDraw(SkCanvas* canvas, SkScalar x, SkScalar y,
SkImagePrivDrawPicture(canvas, fPicture, x, y, paint);
}
+void SkSurface_Picture::onCopyOnWrite(SkImage* cachedImage, SkCanvas*) {
+ // We always spawn a copy of the recording picture when we
+ // are asked for a snapshot, so we never need to do anything here.
+}
+
///////////////////////////////////////////////////////////////////////////////
diff --git a/src/image/SkSurface_Raster.cpp b/src/image/SkSurface_Raster.cpp
index 8afab6b01c..455ef1be5a 100644
--- a/src/image/SkSurface_Raster.cpp
+++ b/src/image/SkSurface_Raster.cpp
@@ -15,13 +15,13 @@ static const size_t kIgnoreRowBytesValue = (size_t)~0;
class SkSurface_Raster : public SkSurface_Base {
public:
- static bool Valid(const SkImage::Info&, SkColorSpace*, size_t rb = kIgnoreRowBytesValue);
+ static bool Valid(const SkImage::Info&, size_t rb = kIgnoreRowBytesValue);
- SkSurface_Raster(const SkImage::Info&, SkColorSpace*, void*, size_t rb);
- SkSurface_Raster(const SkImage::Info&, SkColorSpace*, SkPixelRef*, size_t rb);
+ SkSurface_Raster(const SkImage::Info&, void*, size_t rb);
+ SkSurface_Raster(const SkImage::Info&, SkPixelRef*, size_t rb);
virtual SkCanvas* onNewCanvas() SK_OVERRIDE;
- virtual SkSurface* onNewSurface(const SkImage::Info&, SkColorSpace*) SK_OVERRIDE;
+ virtual SkSurface* onNewSurface(const SkImage::Info&) SK_OVERRIDE;
virtual SkImage* onNewImageShapshot() SK_OVERRIDE;
virtual void onDraw(SkCanvas*, SkScalar x, SkScalar y,
const SkPaint*) SK_OVERRIDE;
@@ -36,8 +36,7 @@ private:
///////////////////////////////////////////////////////////////////////////////
-bool SkSurface_Raster::Valid(const SkImage::Info& info, SkColorSpace* cs,
- size_t rowBytes) {
+bool SkSurface_Raster::Valid(const SkImage::Info& info, size_t rowBytes) {
static const size_t kMaxTotalSize = SK_MaxS32;
bool isOpaque;
@@ -82,8 +81,7 @@ bool SkSurface_Raster::Valid(const SkImage::Info& info, SkColorSpace* cs,
return true;
}
-SkSurface_Raster::SkSurface_Raster(const SkImage::Info& info, SkColorSpace* cs,
- void* pixels, size_t rb)
+SkSurface_Raster::SkSurface_Raster(const SkImage::Info& info, void* pixels, size_t rb)
: INHERITED(info.fWidth, info.fHeight) {
bool isOpaque;
SkBitmap::Config config = SkImageInfoToBitmapConfig(info, &isOpaque);
@@ -94,8 +92,7 @@ SkSurface_Raster::SkSurface_Raster(const SkImage::Info& info, SkColorSpace* cs,
fWeOwnThePixels = false; // We are "Direct"
}
-SkSurface_Raster::SkSurface_Raster(const SkImage::Info& info, SkColorSpace* cs,
- SkPixelRef* pr, size_t rb)
+SkSurface_Raster::SkSurface_Raster(const SkImage::Info& info, SkPixelRef* pr, size_t rb)
: INHERITED(info.fWidth, info.fHeight) {
bool isOpaque;
SkBitmap::Config config = SkImageInfoToBitmapConfig(info, &isOpaque);
@@ -106,7 +103,7 @@ SkSurface_Raster::SkSurface_Raster(const SkImage::Info& info, SkColorSpace* cs,
fWeOwnThePixels = true;
if (!isOpaque) {
- fBitmap.eraseColor(0);
+ fBitmap.eraseColor(SK_ColorTRANSPARENT);
}
}
@@ -114,9 +111,8 @@ SkCanvas* SkSurface_Raster::onNewCanvas() {
return SkNEW_ARGS(SkCanvas, (fBitmap));
}
-SkSurface* SkSurface_Raster::onNewSurface(const SkImage::Info& info,
- SkColorSpace* cs) {
- return SkSurface::NewRaster(info, cs);
+SkSurface* SkSurface_Raster::onNewSurface(const SkImage::Info& info) {
+ return SkSurface::NewRaster(info);
}
void SkSurface_Raster::onDraw(SkCanvas* canvas, SkScalar x, SkScalar y,
@@ -143,21 +139,19 @@ void SkSurface_Raster::onCopyOnWrite(SkImage* image, SkCanvas* canvas) {
///////////////////////////////////////////////////////////////////////////////
-SkSurface* SkSurface::NewRasterDirect(const SkImage::Info& info,
- SkColorSpace* cs,
- void* pixels, size_t rowBytes) {
- if (!SkSurface_Raster::Valid(info, cs, rowBytes)) {
+SkSurface* SkSurface::NewRasterDirect(const SkImage::Info& info, void* pixels, size_t rowBytes) {
+ if (!SkSurface_Raster::Valid(info, rowBytes)) {
return NULL;
}
if (NULL == pixels) {
return NULL;
}
- return SkNEW_ARGS(SkSurface_Raster, (info, cs, pixels, rowBytes));
+ return SkNEW_ARGS(SkSurface_Raster, (info, pixels, rowBytes));
}
-SkSurface* SkSurface::NewRaster(const SkImage::Info& info, SkColorSpace* cs) {
- if (!SkSurface_Raster::Valid(info, cs)) {
+SkSurface* SkSurface::NewRaster(const SkImage::Info& info) {
+ if (!SkSurface_Raster::Valid(info)) {
return NULL;
}
@@ -175,6 +169,6 @@ SkSurface* SkSurface::NewRaster(const SkImage::Info& info, SkColorSpace* cs) {
}
SkAutoTUnref<SkPixelRef> pr(SkNEW_ARGS(SkMallocPixelRef, (pixels, size, NULL, true)));
- return SkNEW_ARGS(SkSurface_Raster, (info, cs, pr, rowBytes));
+ return SkNEW_ARGS(SkSurface_Raster, (info, pr, rowBytes));
}
diff --git a/src/images/SkBitmapFactory.cpp b/src/images/SkBitmapFactory.cpp
new file mode 100644
index 0000000000..4e5d90bd60
--- /dev/null
+++ b/src/images/SkBitmapFactory.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBitmapFactory.h"
+
+#include "SkBitmap.h"
+#include "SkData.h"
+#include "SkImageDecoder.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+
+bool SkBitmapFactory::DecodeBitmap(SkBitmap* dst, const SkData* data, Constraints constraint) {
+ if (NULL == data || data->size() == 0 || dst == NULL) {
+ return false;
+ }
+
+ SkMemoryStream stream(data->data(), data->size());
+ SkAutoTDelete<SkImageDecoder> decoder (SkImageDecoder::Factory(&stream));
+ if (decoder.get() == NULL) {
+ return false;
+ }
+
+ SkBitmap tmp;
+ SkImageDecoder::Mode mode;
+ if (kDecodeBoundsOnly_Constraint == constraint) {
+ mode = SkImageDecoder::kDecodeBounds_Mode;
+ } else {
+ mode = SkImageDecoder::kDecodePixels_Mode;
+ }
+
+ if (decoder->decode(&stream, &tmp, mode)) {
+ tmp.swap(*dst);
+ return true;
+ } else {
+ return false;
+ }
+}
diff --git a/src/images/SkFlipPixelRef.cpp b/src/images/SkFlipPixelRef.cpp
index 2e73ece5ac..11076cf3d8 100644
--- a/src/images/SkFlipPixelRef.cpp
+++ b/src/images/SkFlipPixelRef.cpp
@@ -6,7 +6,7 @@
* found in the LICENSE file.
*/
#include "SkFlipPixelRef.h"
-#include "SkFlattenableBuffers.h"
+#include "SkFlattenable.h"
#include "SkRegion.h"
SkFlipPixelRef::SkFlipPixelRef(SkBitmap::Config config, int width, int height)
@@ -60,21 +60,6 @@ void SkFlipPixelRef::swapPages() {
fMutex.release();
}
-void SkFlipPixelRef::flatten(SkFlattenableWriteBuffer& buffer) const {
- this->INHERITED::flatten(buffer);
- // only need to write page0
- buffer.writeByteArray(fPage0, fSize);
-}
-
-SkFlipPixelRef::SkFlipPixelRef(SkFlattenableReadBuffer& buffer)
- : INHERITED(buffer, NULL) {
- fSize = buffer.getArrayCount();
- fStorage = sk_malloc_throw(fSize << 1);
- fPage0 = fStorage;
- fPage1 = (char*)fStorage + fSize;
- buffer.readByteArray(fPage0);
-}
-
///////////////////////////////////////////////////////////////////////////////
static void copyRect(const SkBitmap& dst, const SkIRect& rect,
@@ -84,7 +69,7 @@ static void copyRect(const SkBitmap& dst, const SkIRect& rect,
const char* srcP = static_cast<const char*>(srcAddr) + offset;
const size_t rb = dst.rowBytes();
const size_t bytes = rect.width() << shift;
-
+
int height = rect.height();
while (--height >= 0) {
memcpy(dstP, srcP, bytes);
@@ -114,10 +99,10 @@ void SkFlipPixelRef::CopyBitsFromAddr(const SkBitmap& dst, const SkRegion& clip,
if (shift < 0) {
return;
}
-
+
const SkIRect bounds = {0, 0, dst.width(), dst.height()};
SkRegion::Cliperator iter(clip, bounds);
-
+
while (!iter.done()) {
copyRect(dst, iter.rect(), srcAddr, shift);
iter.next();
diff --git a/src/images/SkImageDecoder_libpng.cpp b/src/images/SkImageDecoder_libpng.cpp
index dba11f858c..829c3d9854 100644
--- a/src/images/SkImageDecoder_libpng.cpp
+++ b/src/images/SkImageDecoder_libpng.cpp
@@ -17,6 +17,7 @@
#include "SkStream.h"
#include "SkTemplates.h"
#include "SkUtils.h"
+#include "transform_scanline.h"
extern "C" {
#include "png.h"
@@ -819,99 +820,6 @@ static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
}
}
-typedef void (*transform_scanline_proc)(const char* SK_RESTRICT src,
- int width, char* SK_RESTRICT dst);
-
-static void transform_scanline_565(const char* SK_RESTRICT src, int width,
- char* SK_RESTRICT dst) {
- const uint16_t* SK_RESTRICT srcP = (const uint16_t*)src;
- for (int i = 0; i < width; i++) {
- unsigned c = *srcP++;
- *dst++ = SkPacked16ToR32(c);
- *dst++ = SkPacked16ToG32(c);
- *dst++ = SkPacked16ToB32(c);
- }
-}
-
-static void transform_scanline_888(const char* SK_RESTRICT src, int width,
- char* SK_RESTRICT dst) {
- const SkPMColor* SK_RESTRICT srcP = (const SkPMColor*)src;
- for (int i = 0; i < width; i++) {
- SkPMColor c = *srcP++;
- *dst++ = SkGetPackedR32(c);
- *dst++ = SkGetPackedG32(c);
- *dst++ = SkGetPackedB32(c);
- }
-}
-
-static void transform_scanline_444(const char* SK_RESTRICT src, int width,
- char* SK_RESTRICT dst) {
- const SkPMColor16* SK_RESTRICT srcP = (const SkPMColor16*)src;
- for (int i = 0; i < width; i++) {
- SkPMColor16 c = *srcP++;
- *dst++ = SkPacked4444ToR32(c);
- *dst++ = SkPacked4444ToG32(c);
- *dst++ = SkPacked4444ToB32(c);
- }
-}
-
-static void transform_scanline_8888(const char* SK_RESTRICT src, int width,
- char* SK_RESTRICT dst) {
- const SkPMColor* SK_RESTRICT srcP = (const SkPMColor*)src;
- const SkUnPreMultiply::Scale* SK_RESTRICT table =
- SkUnPreMultiply::GetScaleTable();
-
- for (int i = 0; i < width; i++) {
- SkPMColor c = *srcP++;
- unsigned a = SkGetPackedA32(c);
- unsigned r = SkGetPackedR32(c);
- unsigned g = SkGetPackedG32(c);
- unsigned b = SkGetPackedB32(c);
-
- if (0 != a && 255 != a) {
- SkUnPreMultiply::Scale scale = table[a];
- r = SkUnPreMultiply::ApplyScale(scale, r);
- g = SkUnPreMultiply::ApplyScale(scale, g);
- b = SkUnPreMultiply::ApplyScale(scale, b);
- }
- *dst++ = r;
- *dst++ = g;
- *dst++ = b;
- *dst++ = a;
- }
-}
-
-static void transform_scanline_4444(const char* SK_RESTRICT src, int width,
- char* SK_RESTRICT dst) {
- const SkPMColor16* SK_RESTRICT srcP = (const SkPMColor16*)src;
- const SkUnPreMultiply::Scale* SK_RESTRICT table =
- SkUnPreMultiply::GetScaleTable();
-
- for (int i = 0; i < width; i++) {
- SkPMColor16 c = *srcP++;
- unsigned a = SkPacked4444ToA32(c);
- unsigned r = SkPacked4444ToR32(c);
- unsigned g = SkPacked4444ToG32(c);
- unsigned b = SkPacked4444ToB32(c);
-
- if (0 != a && 255 != a) {
- SkUnPreMultiply::Scale scale = table[a];
- r = SkUnPreMultiply::ApplyScale(scale, r);
- g = SkUnPreMultiply::ApplyScale(scale, g);
- b = SkUnPreMultiply::ApplyScale(scale, b);
- }
- *dst++ = r;
- *dst++ = g;
- *dst++ = b;
- *dst++ = a;
- }
-}
-
-static void transform_scanline_index8(const char* SK_RESTRICT src, int width,
- char* SK_RESTRICT dst) {
- memcpy(dst, src, width);
-}
-
static transform_scanline_proc choose_proc(SkBitmap::Config config,
bool hasAlpha) {
// we don't care about search on alpha if we're kIndex8, since only the
@@ -930,7 +838,7 @@ static transform_scanline_proc choose_proc(SkBitmap::Config config,
{ SkBitmap::kARGB_8888_Config, true, transform_scanline_8888 },
{ SkBitmap::kARGB_4444_Config, false, transform_scanline_444 },
{ SkBitmap::kARGB_4444_Config, true, transform_scanline_4444 },
- { SkBitmap::kIndex8_Config, false, transform_scanline_index8 },
+ { SkBitmap::kIndex8_Config, false, transform_scanline_memcpy },
};
for (int i = SK_ARRAY_COUNT(gMap) - 1; i >= 0; --i) {
diff --git a/src/images/SkImages.cpp b/src/images/SkImages.cpp
index 28dfd7f615..0bcc33fa51 100644
--- a/src/images/SkImages.cpp
+++ b/src/images/SkImages.cpp
@@ -6,11 +6,9 @@
*/
#include "SkFlattenable.h"
-#include "SkFlipPixelRef.h"
#include "SkImageRef_GlobalPool.h"
#include "SkImages.h"
void SkImages::InitializeFlattenables() {
- SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkFlipPixelRef)
SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkImageRef_GlobalPool)
}
diff --git a/src/images/transform_scanline.h b/src/images/transform_scanline.h
new file mode 100644
index 0000000000..36efdd84ea
--- /dev/null
+++ b/src/images/transform_scanline.h
@@ -0,0 +1,140 @@
+
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**
+ * Functions to transform scanlines between packed-pixel formats.
+ */
+
+#include "SkBitmap.h"
+#include "SkColor.h"
+#include "SkColorPriv.h"
+#include "SkPreConfig.h"
+#include "SkUnPreMultiply.h"
+
+/**
+ * Function template for transforming scanlines.
+ * Transform 'width' pixels from 'src' buffer into 'dst' buffer,
+ * repacking color channel data as appropriate for the given transformation.
+ */
+typedef void (*transform_scanline_proc)(const char* SK_RESTRICT src,
+ int width, char* SK_RESTRICT dst);
+
+/**
+ * Identity transformation: just copy bytes from src to dst.
+ */
+static void transform_scanline_memcpy(const char* SK_RESTRICT src, int width,
+ char* SK_RESTRICT dst) {
+ memcpy(dst, src, width);
+}
+
+/**
+ * Transform from kRGB_565_Config to 3-bytes-per-pixel RGB.
+ * Alpha channel data is not present in kRGB_565_Config format, so there is no
+ * alpha channel data to preserve.
+ */
+static void transform_scanline_565(const char* SK_RESTRICT src, int width,
+ char* SK_RESTRICT dst) {
+ const uint16_t* SK_RESTRICT srcP = (const uint16_t*)src;
+ for (int i = 0; i < width; i++) {
+ unsigned c = *srcP++;
+ *dst++ = SkPacked16ToR32(c);
+ *dst++ = SkPacked16ToG32(c);
+ *dst++ = SkPacked16ToB32(c);
+ }
+}
+
+/**
+ * Transform from kARGB_8888_Config to 3-bytes-per-pixel RGB.
+ * Alpha channel data, if any, is abandoned.
+ */
+static void transform_scanline_888(const char* SK_RESTRICT src, int width,
+ char* SK_RESTRICT dst) {
+ const SkPMColor* SK_RESTRICT srcP = (const SkPMColor*)src;
+ for (int i = 0; i < width; i++) {
+ SkPMColor c = *srcP++;
+ *dst++ = SkGetPackedR32(c);
+ *dst++ = SkGetPackedG32(c);
+ *dst++ = SkGetPackedB32(c);
+ }
+}
+
+/**
+ * Transform from kARGB_4444_Config to 3-bytes-per-pixel RGB.
+ * Alpha channel data, if any, is abandoned.
+ */
+static void transform_scanline_444(const char* SK_RESTRICT src, int width,
+ char* SK_RESTRICT dst) {
+ const SkPMColor16* SK_RESTRICT srcP = (const SkPMColor16*)src;
+ for (int i = 0; i < width; i++) {
+ SkPMColor16 c = *srcP++;
+ *dst++ = SkPacked4444ToR32(c);
+ *dst++ = SkPacked4444ToG32(c);
+ *dst++ = SkPacked4444ToB32(c);
+ }
+}
+
+/**
+ * Transform from kARGB_8888_Config to 4-bytes-per-pixel RGBA.
+ * (This would be the identity transformation, except for byte-order and
+ * scaling of RGB based on alpha channel).
+ */
+static void transform_scanline_8888(const char* SK_RESTRICT src, int width,
+ char* SK_RESTRICT dst) {
+ const SkPMColor* SK_RESTRICT srcP = (const SkPMColor*)src;
+ const SkUnPreMultiply::Scale* SK_RESTRICT table =
+ SkUnPreMultiply::GetScaleTable();
+
+ for (int i = 0; i < width; i++) {
+ SkPMColor c = *srcP++;
+ unsigned a = SkGetPackedA32(c);
+ unsigned r = SkGetPackedR32(c);
+ unsigned g = SkGetPackedG32(c);
+ unsigned b = SkGetPackedB32(c);
+
+ if (0 != a && 255 != a) {
+ SkUnPreMultiply::Scale scale = table[a];
+ r = SkUnPreMultiply::ApplyScale(scale, r);
+ g = SkUnPreMultiply::ApplyScale(scale, g);
+ b = SkUnPreMultiply::ApplyScale(scale, b);
+ }
+ *dst++ = r;
+ *dst++ = g;
+ *dst++ = b;
+ *dst++ = a;
+ }
+}
+
+/**
+ * Transform from kARGB_8888_Config to 4-bytes-per-pixel RGBA,
+ * with scaling of RGB based on alpha channel.
+ */
+static void transform_scanline_4444(const char* SK_RESTRICT src, int width,
+ char* SK_RESTRICT dst) {
+ const SkPMColor16* SK_RESTRICT srcP = (const SkPMColor16*)src;
+ const SkUnPreMultiply::Scale* SK_RESTRICT table =
+ SkUnPreMultiply::GetScaleTable();
+
+ for (int i = 0; i < width; i++) {
+ SkPMColor16 c = *srcP++;
+ unsigned a = SkPacked4444ToA32(c);
+ unsigned r = SkPacked4444ToR32(c);
+ unsigned g = SkPacked4444ToG32(c);
+ unsigned b = SkPacked4444ToB32(c);
+
+ if (0 != a && 255 != a) {
+ SkUnPreMultiply::Scale scale = table[a];
+ r = SkUnPreMultiply::ApplyScale(scale, r);
+ g = SkUnPreMultiply::ApplyScale(scale, g);
+ b = SkUnPreMultiply::ApplyScale(scale, b);
+ }
+ *dst++ = r;
+ *dst++ = g;
+ *dst++ = b;
+ *dst++ = a;
+ }
+}
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
index 929ca10508..0de011c63c 100644
--- a/src/pdf/SkPDFDevice.cpp
+++ b/src/pdf/SkPDFDevice.cpp
@@ -226,8 +226,8 @@ static void skip_clip_stack_prefix(const SkClipStack& prefix,
SkClipStack::B2TIter prefixIter(prefix);
iter->reset(stack, SkClipStack::Iter::kBottom_IterStart);
- const SkClipStack::B2TIter::Clip* prefixEntry;
- const SkClipStack::B2TIter::Clip* iterEntry;
+ const SkClipStack::Element* prefixEntry;
+ const SkClipStack::Element* iterEntry;
for (prefixEntry = prefixIter.next(); prefixEntry;
prefixEntry = prefixIter.next()) {
@@ -236,12 +236,9 @@ static void skip_clip_stack_prefix(const SkClipStack& prefix,
// Because of SkClipStack does internal intersection, the last clip
// entry may differ.
if (*prefixEntry != *iterEntry) {
- SkASSERT(prefixEntry->fOp == SkRegion::kIntersect_Op);
- SkASSERT(iterEntry->fOp == SkRegion::kIntersect_Op);
- SkASSERT((iterEntry->fRect == NULL) ==
- (prefixEntry->fRect == NULL));
- SkASSERT((iterEntry->fPath == NULL) ==
- (prefixEntry->fPath == NULL));
+ SkASSERT(prefixEntry->getOp() == SkRegion::kIntersect_Op);
+ SkASSERT(iterEntry->getOp() == SkRegion::kIntersect_Op);
+ SkASSERT(iterEntry->getType() == prefixEntry->getType());
// back up the iterator by one
iter->prev();
prefixEntry = prefixIter.next();
@@ -306,10 +303,9 @@ void GraphicStackState::updateClip(const SkClipStack& clipStack,
// If the clip stack does anything other than intersect or if it uses
// an inverse fill type, we have to fall back to the clip region.
bool needRegion = false;
- const SkClipStack::B2TIter::Clip* clipEntry;
+ const SkClipStack::Element* clipEntry;
for (clipEntry = iter.next(); clipEntry; clipEntry = iter.next()) {
- if (clipEntry->fOp != SkRegion::kIntersect_Op ||
- (clipEntry->fPath && clipEntry->fPath->isInverseFillType())) {
+ if (clipEntry->getOp() != SkRegion::kIntersect_Op || clipEntry->isInverseFilled()) {
needRegion = true;
break;
}
@@ -323,19 +319,24 @@ void GraphicStackState::updateClip(const SkClipStack& clipStack,
skip_clip_stack_prefix(fEntries[0].fClipStack, clipStack, &iter);
SkMatrix transform;
transform.setTranslate(translation.fX, translation.fY);
- const SkClipStack::B2TIter::Clip* clipEntry;
+ const SkClipStack::Element* clipEntry;
for (clipEntry = iter.next(); clipEntry; clipEntry = iter.next()) {
- SkASSERT(clipEntry->fOp == SkRegion::kIntersect_Op);
- if (clipEntry->fRect) {
- SkRect translatedClip;
- transform.mapRect(&translatedClip, *clipEntry->fRect);
- emit_clip(NULL, &translatedClip, fContentStream);
- } else if (clipEntry->fPath) {
- SkPath translatedPath;
- clipEntry->fPath->transform(transform, &translatedPath);
- emit_clip(&translatedPath, NULL, fContentStream);
- } else {
- SkASSERT(false);
+ SkASSERT(clipEntry->getOp() == SkRegion::kIntersect_Op);
+ switch (clipEntry->getType()) {
+ case SkClipStack::Element::kRect_Type: {
+ SkRect translatedClip;
+ transform.mapRect(&translatedClip, clipEntry->getRect());
+ emit_clip(NULL, &translatedClip, fContentStream);
+ break;
+ }
+ case SkClipStack::Element::kPath_Type: {
+ SkPath translatedPath;
+ clipEntry->getPath().transform(transform, &translatedPath);
+ emit_clip(&translatedPath, NULL, fContentStream);
+ break;
+ }
+ default:
+ SkASSERT(false);
}
}
}
diff --git a/src/pdf/SkPDFFont.cpp b/src/pdf/SkPDFFont.cpp
index 5cb801feca..9a8f13600b 100644
--- a/src/pdf/SkPDFFont.cpp
+++ b/src/pdf/SkPDFFont.cpp
@@ -701,9 +701,20 @@ SkPDFGlyphSet* SkPDFGlyphSetMap::getGlyphSetForFont(SkPDFFont* font) {
SkPDFFont::~SkPDFFont() {
SkAutoMutexAcquire lock(CanonicalFontsMutex());
- int index;
- if (Find(SkTypeface::UniqueID(fTypeface.get()), fFirstGlyphID, &index) &&
- CanonicalFonts()[index].fFont == this) {
+ int index = -1;
+ for (int i = 0 ; i < CanonicalFonts().count() ; i++) {
+ if (CanonicalFonts()[i].fFont == this) {
+ index = i;
+ }
+ }
+
+ SkDEBUGCODE(int indexFound;)
+ SkASSERT(index == -1 ||
+ (Find(SkTypeface::UniqueID(fTypeface.get()),
+ fFirstGlyphID,
+ &indexFound) &&
+ index == indexFound));
+ if (index >= 0) {
CanonicalFonts().removeShuffle(index);
}
fResources.unrefAll();
@@ -761,6 +772,19 @@ SkPDFFont* SkPDFFont::GetFontResource(SkTypeface* typeface, uint16_t glyphID) {
SkPDFFont* relatedFont = CanonicalFonts()[relatedFontIndex].fFont;
fontMetrics = relatedFont->fontInfo();
relatedFontDescriptor = relatedFont->getFontDescriptor();
+
+ // This only is to catch callers who pass invalid glyph ids.
+ // If glyph id is invalid, then we will create duplicate entries
+ // for True Type fonts.
+ SkAdvancedTypefaceMetrics::FontType fontType =
+ fontMetrics.get() ? fontMetrics.get()->fType :
+ SkAdvancedTypefaceMetrics::kOther_Font;
+
+ if (fontType == SkAdvancedTypefaceMetrics::kType1CID_Font ||
+ fontType == SkAdvancedTypefaceMetrics::kTrueType_Font) {
+ CanonicalFonts()[relatedFontIndex].fFont->ref();
+ return CanonicalFonts()[relatedFontIndex].fFont;
+ }
} else {
SkAdvancedTypefaceMetrics::PerGlyphInfo info;
info = SkAdvancedTypefaceMetrics::kGlyphNames_PerGlyphInfo;
diff --git a/src/pdf/SkPDFFormXObject.cpp b/src/pdf/SkPDFFormXObject.cpp
index c1e2192ac7..e148056afe 100644
--- a/src/pdf/SkPDFFormXObject.cpp
+++ b/src/pdf/SkPDFFormXObject.cpp
@@ -22,6 +22,15 @@ SkPDFFormXObject::SkPDFFormXObject(SkPDFDevice* device) {
// resources).
device->getResources(&fResources, false);
+ // Fail fast if in the tree of resources a child references a parent.
+ // If there is an issue, getResources will end up consuming all memory.
+ // TODO: A better approach might be for all SkPDFObject to keep track
+ // of possible cycles.
+#ifdef SK_DEBUG
+ SkTDArray<SkPDFObject*> dummy_resourceList;
+ getResources(&dummy_resourceList);
+#endif
+
SkRefPtr<SkStream> content = device->content();
content->unref(); // SkRefPtr and content() both took a reference.
setData(content.get());
diff --git a/src/pdf/SkPDFShader.cpp b/src/pdf/SkPDFShader.cpp
index e6f1d7f108..db5beb8fc8 100644
--- a/src/pdf/SkPDFShader.cpp
+++ b/src/pdf/SkPDFShader.cpp
@@ -74,8 +74,9 @@ static void interpolateColorCode(SkScalar range, SkScalar* curColor,
}
for (int i = 0; i < components; i++) {
- // If the next components needs t, make a copy.
- if (dupInput[i]) {
+ // If the next components needs t and this component will consume a
+ // copy, make another copy.
+ if (dupInput[i] && multiplier[i] != 0) {
result->append("dup ");
}
@@ -936,7 +937,7 @@ SkPDFShader::State::State(const SkShader& shader,
fInfo.fColorCount = 0;
fInfo.fColors = NULL;
fInfo.fColorOffsets = NULL;
- shader.getLocalMatrix(&fShaderTransform);
+ fShaderTransform = shader.getLocalMatrix();
fImageTileModes[0] = fImageTileModes[1] = SkShader::kClamp_TileMode;
fType = shader.asAGradient(&fInfo);
diff --git a/src/pipe/SkGPipePriv.h b/src/pipe/SkGPipePriv.h
index 31803ea9b5..b563652cb9 100644
--- a/src/pipe/SkGPipePriv.h
+++ b/src/pipe/SkGPipePriv.h
@@ -37,6 +37,7 @@ enum DrawOps {
kClipPath_DrawOp,
kClipRegion_DrawOp,
kClipRect_DrawOp,
+ kClipRRect_DrawOp,
kConcat_DrawOp,
kDrawBitmap_DrawOp,
kDrawBitmapMatrix_DrawOp,
@@ -44,6 +45,7 @@ enum DrawOps {
kDrawBitmapRectToRect_DrawOp,
kDrawClear_DrawOp,
kDrawData_DrawOp,
+ kDrawOval_DrawOp,
kDrawPaint_DrawOp,
kDrawPath_DrawOp,
kDrawPicture_DrawOp,
@@ -51,6 +53,7 @@ enum DrawOps {
kDrawPosText_DrawOp,
kDrawPosTextH_DrawOp,
kDrawRect_DrawOp,
+ kDrawRRect_DrawOp,
kDrawSprite_DrawOp,
kDrawText_DrawOp,
kDrawTextOnPath_DrawOp,
diff --git a/src/pipe/SkGPipeRead.cpp b/src/pipe/SkGPipeRead.cpp
index 1f13dafa7d..8a86c582c2 100644
--- a/src/pipe/SkGPipeRead.cpp
+++ b/src/pipe/SkGPipeRead.cpp
@@ -21,6 +21,7 @@
#include "SkOrderedReadBuffer.h"
#include "SkPathEffect.h"
#include "SkRasterizer.h"
+#include "SkRRect.h"
#include "SkShader.h"
#include "SkTypeface.h"
#include "SkXfermode.h"
@@ -237,6 +238,14 @@ static void clipRect_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
canvas->clipRect(*rect, (SkRegion::Op)DrawOp_unpackData(op32), doAA);
}
+static void clipRRect_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ SkRRect rrect;
+ reader->readRRect(&rrect);
+ bool doAA = SkToBool(DrawOp_unpackFlags(op32) & kClip_HasAntiAlias_DrawOpFlag);
+ canvas->clipRRect(rrect, (SkRegion::Op)DrawOp_unpackData(op32), doAA);
+}
+
///////////////////////////////////////////////////////////////////////////////
static void setMatrix_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
@@ -332,6 +341,14 @@ static void drawPoints_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
}
}
+static void drawOval_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ const SkRect* rect = skip<SkRect>(reader);
+ if (state->shouldDraw()) {
+ canvas->drawOval(*rect, state->paint());
+ }
+}
+
static void drawRect_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
SkGPipeState* state) {
const SkRect* rect = skip<SkRect>(reader);
@@ -340,6 +357,15 @@ static void drawRect_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
}
}
+static void drawRRect_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ SkRRect rrect;
+ reader->readRRect(&rrect);
+ if (state->shouldDraw()) {
+ canvas->drawRRect(rrect, state->paint());
+ }
+}
+
static void drawPath_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
SkGPipeState* state) {
SkPath path;
@@ -677,6 +703,7 @@ static const ReadProc gReadTable[] = {
clipPath_rp,
clipRegion_rp,
clipRect_rp,
+ clipRRect_rp,
concat_rp,
drawBitmap_rp,
drawBitmapMatrix_rp,
@@ -684,6 +711,7 @@ static const ReadProc gReadTable[] = {
drawBitmapRect_rp,
drawClear_rp,
drawData_rp,
+ drawOval_rp,
drawPaint_rp,
drawPath_rp,
drawPicture_rp,
@@ -691,6 +719,7 @@ static const ReadProc gReadTable[] = {
drawPosText_rp,
drawPosTextH_rp,
drawRect_rp,
+ drawRRect_rp,
drawSprite_rp,
drawText_rp,
drawTextOnPath_rp,
diff --git a/src/pipe/SkGPipeWrite.cpp b/src/pipe/SkGPipeWrite.cpp
index f0b4e0aee6..cfd1e7ceef 100644
--- a/src/pipe/SkGPipeWrite.cpp
+++ b/src/pipe/SkGPipeWrite.cpp
@@ -21,12 +21,17 @@
#include "SkPathEffect.h"
#include "SkPictureFlat.h"
#include "SkRasterizer.h"
+#include "SkRRect.h"
#include "SkShader.h"
#include "SkStream.h"
#include "SkTSearch.h"
#include "SkTypeface.h"
#include "SkWriter32.h"
+enum {
+ kSizeOfFlatRRect = sizeof(SkRect) + 4 * sizeof(SkVector)
+};
+
static bool isCrossProcess(uint32_t flags) {
return SkToBool(flags & SkGPipeWriter::kCrossProcess_Flag);
}
@@ -208,8 +213,8 @@ public:
virtual bool skew(SkScalar sx, SkScalar sy) SK_OVERRIDE;
virtual bool concat(const SkMatrix& matrix) SK_OVERRIDE;
virtual void setMatrix(const SkMatrix& matrix) SK_OVERRIDE;
- virtual bool clipRect(const SkRect& rect, SkRegion::Op op,
- bool doAntiAlias = false) SK_OVERRIDE;
+ virtual bool clipRect(const SkRect&, SkRegion::Op op, bool doAntiAlias = false) SK_OVERRIDE;
+ virtual bool clipRRect(const SkRRect&, SkRegion::Op op, bool doAntiAlias = false) SK_OVERRIDE;
virtual bool clipPath(const SkPath& path, SkRegion::Op op,
bool doAntiAlias = false) SK_OVERRIDE;
virtual bool clipRegion(const SkRegion& region, SkRegion::Op op) SK_OVERRIDE;
@@ -217,7 +222,9 @@ public:
virtual void drawPaint(const SkPaint& paint) SK_OVERRIDE;
virtual void drawPoints(PointMode, size_t count, const SkPoint pts[],
const SkPaint&) SK_OVERRIDE;
+ virtual void drawOval(const SkRect&, const SkPaint&) SK_OVERRIDE;
virtual void drawRect(const SkRect& rect, const SkPaint&) SK_OVERRIDE;
+ virtual void drawRRect(const SkRRect&, const SkPaint&) SK_OVERRIDE;
virtual void drawPath(const SkPath& path, const SkPaint&) SK_OVERRIDE;
virtual void drawBitmap(const SkBitmap&, SkScalar left, SkScalar top,
const SkPaint*) SK_OVERRIDE;
@@ -625,6 +632,17 @@ bool SkGPipeCanvas::clipRect(const SkRect& rect, SkRegion::Op rgnOp,
return this->INHERITED::clipRect(rect, rgnOp, doAntiAlias);
}
+bool SkGPipeCanvas::clipRRect(const SkRRect& rrect, SkRegion::Op rgnOp,
+ bool doAntiAlias) {
+ NOTIFY_SETUP(this);
+ if (this->needOpBytes(kSizeOfFlatRRect)) {
+ unsigned flags = doAntiAlias & kClip_HasAntiAlias_DrawOpFlag;
+ this->writeOp(kClipRRect_DrawOp, flags, rgnOp);
+ fWriter.writeRRect(rrect);
+ }
+ return this->INHERITED::clipRRect(rrect, rgnOp, doAntiAlias);
+}
+
bool SkGPipeCanvas::clipPath(const SkPath& path, SkRegion::Op rgnOp,
bool doAntiAlias) {
NOTIFY_SETUP(this);
@@ -683,6 +701,15 @@ void SkGPipeCanvas::drawPoints(PointMode mode, size_t count,
}
}
+void SkGPipeCanvas::drawOval(const SkRect& rect, const SkPaint& paint) {
+ NOTIFY_SETUP(this);
+ this->writePaint(paint);
+ if (this->needOpBytes(sizeof(SkRect))) {
+ this->writeOp(kDrawOval_DrawOp);
+ fWriter.writeRect(rect);
+ }
+}
+
void SkGPipeCanvas::drawRect(const SkRect& rect, const SkPaint& paint) {
NOTIFY_SETUP(this);
this->writePaint(paint);
@@ -692,6 +719,15 @@ void SkGPipeCanvas::drawRect(const SkRect& rect, const SkPaint& paint) {
}
}
+void SkGPipeCanvas::drawRRect(const SkRRect& rrect, const SkPaint& paint) {
+ NOTIFY_SETUP(this);
+ this->writePaint(paint);
+ if (this->needOpBytes(kSizeOfFlatRRect)) {
+ this->writeOp(kDrawRRect_DrawOp);
+ fWriter.writeRRect(rrect);
+ }
+}
+
void SkGPipeCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
NOTIFY_SETUP(this);
this->writePaint(paint);
diff --git a/src/ports/FontHostConfiguration_android.cpp b/src/ports/FontHostConfiguration_android.cpp
index 420ad1ca7b..98928c6706 100644
--- a/src/ports/FontHostConfiguration_android.cpp
+++ b/src/ports/FontHostConfiguration_android.cpp
@@ -1,19 +1,9 @@
-/* libs/graphics/ports/FontHostConfiguration_android.cpp
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
+/*
+ * Copyright 2011 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
#include "FontHostConfiguration_android.h"
#include "SkLanguage.h"
diff --git a/src/ports/FontHostConfiguration_android.h b/src/ports/FontHostConfiguration_android.h
index 6734b08e33..9f558bc4e2 100644
--- a/src/ports/FontHostConfiguration_android.h
+++ b/src/ports/FontHostConfiguration_android.h
@@ -1,19 +1,10 @@
-/* libs/graphics/ports/FontHostConfiguration_android.h
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
+/*
+ * Copyright 2011 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
#ifndef FONTHOSTCONFIGURATION_ANDROID_H_
#define FONTHOSTCONFIGURATION_ANDROID_H_
diff --git a/src/ports/SkDebug_android.cpp b/src/ports/SkDebug_android.cpp
index 8e7d1d48a3..b9b5665a3c 100644
--- a/src/ports/SkDebug_android.cpp
+++ b/src/ports/SkDebug_android.cpp
@@ -14,9 +14,22 @@ static const size_t kBufferSize = 256;
#define LOG_TAG "skia"
#include <android/log.h>
+static bool gSkDebugToStdOut = false;
+
+extern "C" void AndroidSkDebugToStdOut(bool debugToStdOut) {
+ gSkDebugToStdOut = debugToStdOut;
+}
+
void SkDebugf(const char format[], ...) {
va_list args;
va_start(args, format);
__android_log_vprint(ANDROID_LOG_DEBUG, LOG_TAG, format, args);
+
+ // Print debug output to stdout as well. This is useful for command
+ // line applications (e.g. skia_launcher)
+ if (gSkDebugToStdOut) {
+ vprintf(format, args);
+ }
+
va_end(args);
}
diff --git a/src/ports/SkDebug_nacl.cpp b/src/ports/SkDebug_nacl.cpp
new file mode 100644
index 0000000000..b1d260f0ee
--- /dev/null
+++ b/src/ports/SkDebug_nacl.cpp
@@ -0,0 +1,39 @@
+
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+
+#include "SkTypes.h"
+
+static const size_t kBufferSize = 2048;
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "ppapi/cpp/instance.h"
+#include "ppapi/cpp/var.h"
+
+extern pp::Instance* gPluginInstance;
+
+namespace {
+static const char* kLogPrefix = "SkDebugf:";
+}
+
+void SkDebugf(const char format[], ...) {
+ if (gPluginInstance) {
+ char buffer[kBufferSize + 1];
+ va_list args;
+ va_start(args, format);
+ sprintf(buffer, kLogPrefix);
+ vsnprintf(buffer + strlen(kLogPrefix), kBufferSize, format, args);
+ va_end(args);
+ pp::Var msg = pp::Var(buffer);
+ gPluginInstance->PostMessage(msg);
+ }
+}
+
diff --git a/src/ports/SkFontHost_FreeType.cpp b/src/ports/SkFontHost_FreeType.cpp
index 9b6cd3cf03..2ed97cd8a5 100644
--- a/src/ports/SkFontHost_FreeType.cpp
+++ b/src/ports/SkFontHost_FreeType.cpp
@@ -144,6 +144,19 @@ static bool InitFreetype() {
return true;
}
+// Lazy, once, wrapper to ask the FreeType Library if it can support LCD text
+static bool is_lcd_supported() {
+ if (!gLCDSupportValid) {
+ SkAutoMutexAcquire ac(gFTMutex);
+
+ if (!gLCDSupportValid) {
+ InitFreetype();
+ FT_Done_FreeType(gFTLibrary);
+ }
+ }
+ return gLCDSupport;
+}
+
class SkScalerContext_FreeType : public SkScalerContext_FreeType_Base {
public:
SkScalerContext_FreeType(const SkDescriptor* desc);
@@ -156,15 +169,15 @@ public:
}
protected:
- virtual unsigned generateGlyphCount();
- virtual uint16_t generateCharToGlyph(SkUnichar uni);
- virtual void generateAdvance(SkGlyph* glyph);
- virtual void generateMetrics(SkGlyph* glyph);
- virtual void generateImage(const SkGlyph& glyph, SkMaskGamma::PreBlend* maskPreBlend);
- virtual void generatePath(const SkGlyph& glyph, SkPath* path);
+ virtual unsigned generateGlyphCount() SK_OVERRIDE;
+ virtual uint16_t generateCharToGlyph(SkUnichar uni) SK_OVERRIDE;
+ virtual void generateAdvance(SkGlyph* glyph) SK_OVERRIDE;
+ virtual void generateMetrics(SkGlyph* glyph) SK_OVERRIDE;
+ virtual void generateImage(const SkGlyph& glyph) SK_OVERRIDE;
+ virtual void generatePath(const SkGlyph& glyph, SkPath* path) SK_OVERRIDE;
virtual void generateFontMetrics(SkPaint::FontMetrics* mx,
- SkPaint::FontMetrics* my);
- virtual SkUnichar generateGlyphToChar(uint16_t glyph);
+ SkPaint::FontMetrics* my) SK_OVERRIDE;
+ virtual SkUnichar generateGlyphToChar(uint16_t glyph) SK_OVERRIDE;
private:
SkFaceRec* fFaceRec;
@@ -575,7 +588,7 @@ SkAdvancedTypefaceMetrics* SkFontHost::GetAdvancedTypefaceMetrics(
getAdvances(face, gID, advanceCount, FT_LOAD_NO_SCALE,
advances);
for (int i = 0; i < advanceCount; i++) {
- int16_t advance = advances[gID + i];
+ int16_t advance = advances[i];
info->fGlyphWidths->fAdvance.append(1, &advance);
}
}
@@ -648,14 +661,7 @@ void SkFontHost::FilterRec(SkScalerContext::Rec* rec, SkTypeface*) {
rec->fTextSize = SkIntToScalar(1 << 14);
}
- SkAutoMutexAcquire ac(gFTMutex);
-
- if (!gLCDSupportValid) {
- InitFreetype();
- FT_Done_FreeType(gFTLibrary);
- }
-
- if (!gLCDSupport && isLCD(*rec)) {
+ if (!is_lcd_supported() && isLCD(*rec)) {
// If the runtime Freetype library doesn't support LCD mode, we disable
// it here.
rec->fMaskFormat = SkMask::kA8_Format;
@@ -672,12 +678,10 @@ void SkFontHost::FilterRec(SkScalerContext::Rec* rec, SkTypeface*) {
}
}
-#ifndef SK_IGNORE_ROTATED_FREETYPE_FIX
// rotated text looks bad with hinting, so we disable it as needed
if (!isAxisAligned(*rec)) {
h = SkPaint::kNo_Hinting;
}
-#endif
rec->setHinting(h);
#ifndef SK_GAMMA_APPLY_TO_A8
@@ -1044,8 +1048,10 @@ void SkScalerContext_FreeType::generateMetrics(SkGlyph* glyph) {
err = FT_Load_Glyph( fFace, glyph->getGlyphID(fBaseGlyphCount), fLoadGlyphFlags );
if (err != 0) {
- SkDEBUGF(("SkScalerContext_FreeType::generateMetrics(%x): FT_Load_Glyph(glyph:%d flags:%d) returned 0x%x\n",
+#if 0
+ SkDEBUGF(("SkScalerContext_FreeType::generateMetrics(%x): FT_Load_Glyph(glyph:%d flags:%x) returned 0x%x\n",
fFaceRec->fFontID, glyph->getGlyphID(fBaseGlyphCount), fLoadGlyphFlags, err));
+#endif
ERROR:
glyph->zeroMetrics();
return;
@@ -1136,7 +1142,7 @@ void SkScalerContext_FreeType::generateMetrics(SkGlyph* glyph) {
}
-void SkScalerContext_FreeType::generateImage(const SkGlyph& glyph, SkMaskGamma::PreBlend* maskPreBlend) {
+void SkScalerContext_FreeType::generateImage(const SkGlyph& glyph) {
SkAutoMutexAcquire ac(gFTMutex);
FT_Error err;
@@ -1154,7 +1160,7 @@ void SkScalerContext_FreeType::generateImage(const SkGlyph& glyph, SkMaskGamma::
return;
}
- generateGlyphImage(fFace, glyph, maskPreBlend);
+ generateGlyphImage(fFace, glyph);
}
diff --git a/src/ports/SkFontHost_FreeType_common.cpp b/src/ports/SkFontHost_FreeType_common.cpp
index 702cc07817..cc948029e1 100644
--- a/src/ports/SkFontHost_FreeType_common.cpp
+++ b/src/ports/SkFontHost_FreeType_common.cpp
@@ -130,20 +130,7 @@ static void copyFT2LCD16(const SkGlyph& glyph, const FT_Bitmap& bitmap,
}
}
-void SkScalerContext_FreeType_Base::generateGlyphImage(FT_Face face,
- const SkGlyph& glyph,
- SkMaskGamma::PreBlend* maskPreBlend)
-{
- //Must be careful not to use these if maskPreBlend == NULL
- const uint8_t* tableR = NULL;
- const uint8_t* tableG = NULL;
- const uint8_t* tableB = NULL;
- if (maskPreBlend) {
- tableR = maskPreBlend->fR;
- tableG = maskPreBlend->fG;
- tableB = maskPreBlend->fB;
- }
-
+void SkScalerContext_FreeType_Base::generateGlyphImage(FT_Face face, const SkGlyph& glyph) {
const bool doBGR = SkToBool(fRec.fFlags & SkScalerContext::kLCD_BGROrder_Flag);
const bool doVert = SkToBool(fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag);
@@ -178,12 +165,12 @@ void SkScalerContext_FreeType_Base::generateGlyphImage(FT_Face face,
if (SkMask::kLCD16_Format == glyph.fMaskFormat) {
FT_Render_Glyph(face->glyph, doVert ? FT_RENDER_MODE_LCD_V : FT_RENDER_MODE_LCD);
- if (maskPreBlend) {
+ if (fPreBlend.isApplicable()) {
copyFT2LCD16<true>(glyph, face->glyph->bitmap, doBGR, doVert,
- tableR, tableG, tableB);
+ fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
} else {
copyFT2LCD16<false>(glyph, face->glyph->bitmap, doBGR, doVert,
- tableR, tableG, tableB);
+ fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
}
} else {
target.width = glyph.fWidth;
@@ -249,12 +236,12 @@ void SkScalerContext_FreeType_Base::generateGlyphImage(FT_Face face,
dst += glyph.rowBytes();
}
} else if (SkMask::kLCD16_Format == glyph.fMaskFormat) {
- if (maskPreBlend) {
+ if (fPreBlend.isApplicable()) {
copyFT2LCD16<true>(glyph, face->glyph->bitmap, doBGR, doVert,
- tableR, tableG, tableB);
+ fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
} else {
copyFT2LCD16<false>(glyph, face->glyph->bitmap, doBGR, doVert,
- tableR, tableG, tableB);
+ fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
}
} else {
SkDEBUGFAIL("unknown glyph bitmap transform needed");
@@ -270,13 +257,13 @@ void SkScalerContext_FreeType_Base::generateGlyphImage(FT_Face face,
// We used to always do this pre-USE_COLOR_LUMINANCE, but with colorlum,
// it is optional
#if defined(SK_GAMMA_APPLY_TO_A8)
- if (SkMask::kA8_Format == glyph.fMaskFormat && maskPreBlend) {
+ if (SkMask::kA8_Format == glyph.fMaskFormat && fPreBlend.isApplicable()) {
uint8_t* SK_RESTRICT dst = (uint8_t*)glyph.fImage;
unsigned rowBytes = glyph.rowBytes();
for (int y = glyph.fHeight - 1; y >= 0; --y) {
for (int x = glyph.fWidth - 1; x >= 0; --x) {
- dst[x] = tableG[dst[x]];
+ dst[x] = fPreBlend.fG[dst[x]];
}
dst += rowBytes;
}
diff --git a/src/ports/SkFontHost_FreeType_common.h b/src/ports/SkFontHost_FreeType_common.h
index d5f2c08ab2..01100de43e 100644
--- a/src/ports/SkFontHost_FreeType_common.h
+++ b/src/ports/SkFontHost_FreeType_common.h
@@ -36,7 +36,7 @@ public:
{}
protected:
- void generateGlyphImage(FT_Face face, const SkGlyph& glyph, SkMaskGamma::PreBlend* maskPreBlend);
+ void generateGlyphImage(FT_Face face, const SkGlyph& glyph);
void generateGlyphPath(FT_Face face, const SkGlyph& glyph, SkPath* path);
void emboldenOutline(FT_Face face, FT_Outline* outline);
};
diff --git a/src/ports/SkFontHost_android.cpp b/src/ports/SkFontHost_android.cpp
index ae5fe8d36b..5788ab69c3 100644
--- a/src/ports/SkFontHost_android.cpp
+++ b/src/ports/SkFontHost_android.cpp
@@ -1,19 +1,9 @@
-/* libs/graphics/ports/SkFontHost_android.cpp
-**
-** Copyright 2006, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
#include "SkFontHost.h"
#include "SkFontDescriptor.h"
diff --git a/src/ports/SkFontHost_fontconfig.cpp b/src/ports/SkFontHost_fontconfig.cpp
index e028a012a5..0f79a30ea3 100644
--- a/src/ports/SkFontHost_fontconfig.cpp
+++ b/src/ports/SkFontHost_fontconfig.cpp
@@ -1,4 +1,3 @@
-
/*
* Copyright 2008 Google Inc.
*
@@ -6,16 +5,6 @@
* found in the LICENSE file.
*/
-
-// -----------------------------------------------------------------------------
-// This file provides implementations of the font resolution members of
-// SkFontHost by using the fontconfig[1] library. Fontconfig is usually found
-// on Linux systems and handles configuration, parsing and caching issues
-// involved with enumerating and matching fonts.
-//
-// [1] http://fontconfig.org
-// -----------------------------------------------------------------------------
-
#include <map>
#include <string>
@@ -24,81 +13,69 @@
#include "SkFontHost.h"
#include "SkStream.h"
-// This is an extern from SkFontHost_FreeType
+/** An extern from SkFontHost_FreeType. */
SkTypeface::Style find_name_and_style(SkStream* stream, SkString* name);
-// -----------------------------------------------------------------------------
-// The rest of Skia requires that fonts be identified by a unique unsigned id
-// and that we be able to load them given the id. What we actually get from
-// fontconfig is the filename of the font so we keep a locked map from
-// filenames to fileid numbers and back.
-//
-// Note that there's also a unique id in the SkTypeface. This is unique over
-// both filename and style. Thus we encode that id as (fileid << 8) | style.
-// Although truetype fonts can support multiple faces in a single file, at the
-// moment Skia doesn't.
-// -----------------------------------------------------------------------------
+/** This lock must be held while modifying global_fc_* globals. */
SK_DECLARE_STATIC_MUTEX(global_fc_map_lock);
+
+/** Map from file names to file ids. */
static std::map<std::string, unsigned> global_fc_map;
+/** Map from file ids to file names. */
static std::map<unsigned, std::string> global_fc_map_inverted;
-static std::map<uint32_t, SkTypeface *> global_fc_typefaces;
+/** The next file id. */
static unsigned global_fc_map_next_id = 0;
-static unsigned UniqueIdToFileId(unsigned uniqueid)
-{
+/**
+ * Check to see if the filename has already been assigned a fileid and, if so, use it.
+ * Otherwise, assign one. Return the resulting fileid.
+ */
+static unsigned FileIdFromFilename(const char* filename) {
+ SkAutoMutexAcquire ac(global_fc_map_lock);
+
+ std::map<std::string, unsigned>::const_iterator i = global_fc_map.find(filename);
+ if (i == global_fc_map.end()) {
+ const unsigned fileid = global_fc_map_next_id++;
+ global_fc_map[filename] = fileid;
+ global_fc_map_inverted[fileid] = filename;
+ return fileid;
+ } else {
+ return i->second;
+ }
+}
+
+static unsigned FileIdFromUniqueId(unsigned uniqueid) {
return uniqueid >> 8;
}
-static SkTypeface::Style UniqueIdToStyle(unsigned uniqueid)
-{
+static SkTypeface::Style StyleFromUniqueId(unsigned uniqueid) {
return static_cast<SkTypeface::Style>(uniqueid & 0xff);
}
-static unsigned FileIdAndStyleToUniqueId(unsigned fileid,
- SkTypeface::Style style)
-{
+static unsigned UniqueIdFromFileIdAndStyle(unsigned fileid, SkTypeface::Style style) {
SkASSERT((style & 0xff) == style);
return (fileid << 8) | static_cast<int>(style);
}
-// -----------------------------------------------------------------------------
-// Normally we only return exactly the font asked for. In last-resort cases,
-// the request is for one of the basic font names "Sans", "Serif" or
-// "Monospace". This function tells you whether a given request is for such a
-// fallback.
-// -----------------------------------------------------------------------------
-static bool IsFallbackFontAllowed(const char* request)
-{
- return strcmp(request, "Sans") == 0 ||
- strcmp(request, "Serif") == 0 ||
- strcmp(request, "Monospace") == 0;
-}
-
class FontConfigTypeface : public SkTypeface {
public:
- FontConfigTypeface(Style style, uint32_t id)
- : SkTypeface(style, id)
- { }
+ FontConfigTypeface(Style style, uint32_t id) : SkTypeface(style, id) { }
};
-// -----------------------------------------------------------------------------
-// Find a matching font where @type (one of FC_*) is equal to @value. For a
-// list of types, see http://fontconfig.org/fontconfig-devel/x19.html#AEN27.
-// The variable arguments are a list of triples, just like the first three
-// arguments, and must be NULL terminated.
-//
-// For example,
-// FontMatchString(FC_FILE, FcTypeString, "/usr/share/fonts/myfont.ttf",
-// NULL);
-// -----------------------------------------------------------------------------
-static FcPattern* FontMatch(const char* type, FcType vtype, const void* value,
- ...)
-{
+/**
+ * Find a matching font where @type (one of FC_*) is equal to @value. For a
+ * list of types, see http://fontconfig.org/fontconfig-devel/x19.html#AEN27.
+ * The variable arguments are a list of triples, just like the first three
+ * arguments, and must be NULL terminated.
+ *
+ * For example,
+ * FontMatchString(FC_FILE, FcTypeString, "/usr/share/fonts/myfont.ttf", NULL);
+ */
+static FcPattern* FontMatch(const char* type, FcType vtype, const void* value, ...) {
va_list ap;
va_start(ap, value);
FcPattern* pattern = FcPatternCreate();
- const char* family_requested = NULL;
for (;;) {
FcValue fcvalue;
@@ -113,10 +90,7 @@ static FcPattern* FontMatch(const char* type, FcType vtype, const void* value,
default:
SkDEBUGFAIL("FontMatch unhandled type");
}
- FcPatternAdd(pattern, type, fcvalue, 0);
-
- if (vtype == FcTypeString && strcmp(type, FC_FAMILY) == 0)
- family_requested = (const char*) value;
+ FcPatternAdd(pattern, type, fcvalue, FcFalse);
type = va_arg(ap, const char *);
if (!type)
@@ -127,84 +101,16 @@ static FcPattern* FontMatch(const char* type, FcType vtype, const void* value,
};
va_end(ap);
- FcConfigSubstitute(0, pattern, FcMatchPattern);
+ FcConfigSubstitute(NULL, pattern, FcMatchPattern);
FcDefaultSubstitute(pattern);
- // Font matching:
- // CSS often specifies a fallback list of families:
- // font-family: a, b, c, serif;
- // However, fontconfig will always do its best to find *a* font when asked
- // for something so we need a way to tell if the match which it has found is
- // "good enough" for us. Otherwise, we can return NULL which gets piped up
- // and lets WebKit know to try the next CSS family name. However, fontconfig
- // configs allow substitutions (mapping "Arial -> Helvetica" etc) and we
- // wish to support that.
- //
- // Thus, if a specific family is requested we set @family_requested. Then we
- // record two strings: the family name after config processing and the
- // family name after resolving. If the two are equal, it's a good match.
- //
- // So consider the case where a user has mapped Arial to Helvetica in their
- // config.
- // requested family: "Arial"
- // post_config_family: "Helvetica"
- // post_match_family: "Helvetica"
- // -> good match
- //
- // and for a missing font:
- // requested family: "Monaco"
- // post_config_family: "Monaco"
- // post_match_family: "Times New Roman"
- // -> BAD match
- //
- // However, we special-case fallback fonts; see IsFallbackFontAllowed().
- FcChar8* post_config_family;
- FcPatternGetString(pattern, FC_FAMILY, 0, &post_config_family);
-
FcResult result;
- FcPattern* match = FcFontMatch(0, pattern, &result);
- if (!match) {
- FcPatternDestroy(pattern);
- return NULL;
- }
-
- FcChar8* post_match_family;
- FcPatternGetString(match, FC_FAMILY, 0, &post_match_family);
- const bool family_names_match =
- !family_requested ?
- true :
- strcasecmp((char *)post_config_family, (char *)post_match_family) == 0;
-
+ FcPattern* match = FcFontMatch(NULL, pattern, &result);
FcPatternDestroy(pattern);
- if (!family_names_match && !IsFallbackFontAllowed(family_requested)) {
- FcPatternDestroy(match);
- return NULL;
- }
-
return match;
}
-// -----------------------------------------------------------------------------
-// Check to see if the filename has already been assigned a fileid and, if so,
-// use it. Otherwise, assign one. Return the resulting fileid.
-// -----------------------------------------------------------------------------
-static unsigned FileIdFromFilename(const char* filename)
-{
- SkAutoMutexAcquire ac(global_fc_map_lock);
-
- std::map<std::string, unsigned>::const_iterator i =
- global_fc_map.find(filename);
- if (i == global_fc_map.end()) {
- const unsigned fileid = global_fc_map_next_id++;
- global_fc_map[filename] = fileid;
- global_fc_map_inverted[fileid] = filename;
- return fileid;
- } else {
- return i->second;
- }
-}
-
// static
SkTypeface* SkFontHost::CreateTypeface(const SkTypeface* familyFace,
const char familyName[],
@@ -215,7 +121,9 @@ SkTypeface* SkFontHost::CreateTypeface(const SkTypeface* familyFace,
{
SkAutoMutexAcquire ac(global_fc_map_lock);
- FcInit();
+ if (FcTrue != FcInit()) {
+ SkASSERT(false && "Could not initialize fontconfig.");
+ }
}
if (familyFace) {
@@ -224,18 +132,17 @@ SkTypeface* SkFontHost::CreateTypeface(const SkTypeface* familyFace,
// familyname of the font.
SkAutoMutexAcquire ac(global_fc_map_lock);
- const unsigned fileid = UniqueIdToFileId(familyFace->uniqueID());
- std::map<unsigned, std::string>::const_iterator i =
- global_fc_map_inverted.find(fileid);
- if (i == global_fc_map_inverted.end())
+ const unsigned fileid = FileIdFromUniqueId(familyFace->uniqueID());
+ std::map<unsigned, std::string>::const_iterator i = global_fc_map_inverted.find(fileid);
+ if (i == global_fc_map_inverted.end()) {
return NULL;
+ }
- FcInit();
- face_match = FontMatch(FC_FILE, FcTypeString, i->second.c_str(),
- NULL);
-
- if (!face_match)
+ face_match = FontMatch(FC_FILE, FcTypeString, i->second.c_str(), NULL);
+ if (!face_match) {
return NULL;
+ }
+
FcChar8* family;
if (FcPatternGetString(face_match, FC_FAMILY, 0, &family)) {
FcPatternDestroy(face_match);
@@ -247,21 +154,23 @@ SkTypeface* SkFontHost::CreateTypeface(const SkTypeface* familyFace,
resolved_family_name = reinterpret_cast<char*>(family);
} else if (familyName) {
resolved_family_name = familyName;
+ }
+
+ const int bold = (style & SkTypeface::kBold) ? FC_WEIGHT_BOLD : FC_WEIGHT_NORMAL;
+ const int italic = (style & SkTypeface::kItalic) ? FC_SLANT_ITALIC : FC_SLANT_ROMAN;
+
+ FcPattern* match;
+ if (resolved_family_name) {
+ match = FontMatch(FC_FAMILY, FcTypeString, resolved_family_name,
+ FC_WEIGHT, FcTypeInteger, bold,
+ FC_SLANT, FcTypeInteger, italic,
+ NULL);
} else {
- return NULL;
+ match = FontMatch(FC_WEIGHT, FcTypeInteger, reinterpret_cast<void*>(bold),
+ FC_SLANT, FcTypeInteger, italic,
+ NULL);
}
- // At this point, we have a resolved_family_name from somewhere
- SkASSERT(resolved_family_name);
-
- const int bold = style & SkTypeface::kBold ?
- FC_WEIGHT_BOLD : FC_WEIGHT_NORMAL;
- const int italic = style & SkTypeface::kItalic ?
- FC_SLANT_ITALIC : FC_SLANT_ROMAN;
- FcPattern* match = FontMatch(FC_FAMILY, FcTypeString, resolved_family_name,
- FC_WEIGHT, FcTypeInteger, bold,
- FC_SLANT, FcTypeInteger, italic,
- NULL);
if (face_match)
FcPatternDestroy(face_match);
@@ -276,53 +185,43 @@ SkTypeface* SkFontHost::CreateTypeface(const SkTypeface* familyFace,
// Now @filename is pointing into @match
const unsigned fileid = FileIdFromFilename(reinterpret_cast<char*>(filename));
- const unsigned id = FileIdAndStyleToUniqueId(fileid, style);
+ const unsigned id = UniqueIdFromFileIdAndStyle(fileid, style);
SkTypeface* typeface = SkNEW_ARGS(FontConfigTypeface, (style, id));
FcPatternDestroy(match);
- {
- SkAutoMutexAcquire ac(global_fc_map_lock);
- global_fc_typefaces[id] = typeface;
- }
-
return typeface;
}
// static
-SkTypeface* SkFontHost::CreateTypefaceFromStream(SkStream* stream)
-{
+SkTypeface* SkFontHost::CreateTypefaceFromStream(SkStream* stream) {
SkDEBUGFAIL("SkFontHost::CreateTypefaceFromStream unimplemented");
return NULL;
}
// static
-SkTypeface* SkFontHost::CreateTypefaceFromFile(const char path[])
-{
+SkTypeface* SkFontHost::CreateTypefaceFromFile(const char path[]) {
SkDEBUGFAIL("SkFontHost::CreateTypefaceFromFile unimplemented");
return NULL;
}
// static
-SkStream* SkFontHost::OpenStream(uint32_t id)
-{
+SkStream* SkFontHost::OpenStream(uint32_t id) {
SkAutoMutexAcquire ac(global_fc_map_lock);
- const unsigned fileid = UniqueIdToFileId(id);
+ const unsigned fileid = FileIdFromUniqueId(id);
- std::map<unsigned, std::string>::const_iterator i =
- global_fc_map_inverted.find(fileid);
- if (i == global_fc_map_inverted.end())
+ std::map<unsigned, std::string>::const_iterator i = global_fc_map_inverted.find(fileid);
+ if (i == global_fc_map_inverted.end()) {
return NULL;
+ }
return SkNEW_ARGS(SkFILEStream, (i->second.c_str()));
}
-size_t SkFontHost::GetFileName(SkFontID fontID, char path[], size_t length,
- int32_t* index) {
+size_t SkFontHost::GetFileName(SkFontID fontID, char path[], size_t length, int32_t* index) {
SkAutoMutexAcquire ac(global_fc_map_lock);
- const unsigned fileid = UniqueIdToFileId(fontID);
+ const unsigned fileid = FileIdFromUniqueId(fontID);
- std::map<unsigned, std::string>::const_iterator i =
- global_fc_map_inverted.find(fileid);
+ std::map<unsigned, std::string>::const_iterator i = global_fc_map_inverted.find(fileid);
if (i == global_fc_map_inverted.end()) {
return 0;
}
diff --git a/src/ports/SkFontHost_mac_coretext.cpp b/src/ports/SkFontHost_mac_coretext.cpp
index e385337388..527b9816ce 100644
--- a/src/ports/SkFontHost_mac_coretext.cpp
+++ b/src/ports/SkFontHost_mac_coretext.cpp
@@ -26,6 +26,12 @@
#include "SkFloatingPoint.h"
#include "SkGlyph.h"
#include "SkMaskGamma.h"
+#include "SkSFNTHeader.h"
+#include "SkOTTable_glyf.h"
+#include "SkOTTable_head.h"
+#include "SkOTTable_hhea.h"
+#include "SkOTTable_loca.h"
+#include "SkOTUtils.h"
#include "SkPaint.h"
#include "SkPath.h"
#include "SkString.h"
@@ -37,19 +43,60 @@
class SkScalerContext_Mac;
-static void CFSafeRelease(CFTypeRef obj) {
- if (obj) {
- CFRelease(obj);
+// Being templated and taking const T* prevents calling
+// CFSafeRelease(autoCFRelease) through implicit conversion.
+template <typename T> static void CFSafeRelease(/*CFTypeRef*/const T* cfTypeRef) {
+ if (cfTypeRef) {
+ CFRelease(cfTypeRef);
}
}
-class AutoCFRelease : SkNoncopyable {
+// Being templated and taking const T* prevents calling
+// CFSafeRetain(autoCFRelease) through implicit conversion.
+template <typename T> static void CFSafeRetain(/*CFTypeRef*/const T* cfTypeRef) {
+ if (cfTypeRef) {
+ CFRetain(cfTypeRef);
+ }
+}
+
+/** Acts like a CFRef, but calls CFSafeRelease when it goes out of scope. */
+template<typename CFRef> class AutoCFRelease : private SkNoncopyable {
public:
- AutoCFRelease(CFTypeRef obj) : fObj(obj) {}
- ~AutoCFRelease() { CFSafeRelease(fObj); }
+ explicit AutoCFRelease(CFRef cfRef = NULL) : fCFRef(cfRef) { }
+ ~AutoCFRelease() { CFSafeRelease(fCFRef); }
+
+ void reset(CFRef that = NULL) {
+ CFSafeRetain(that);
+ CFSafeRelease(fCFRef);
+ fCFRef = that;
+ }
+
+ AutoCFRelease& operator =(CFRef that) {
+ reset(that);
+ return *this;
+ }
+
+ operator CFRef() const { return fCFRef; }
+ CFRef get() const { return fCFRef; }
private:
- CFTypeRef fObj;
+ CFRef fCFRef;
+};
+
+template<typename T> class AutoCGTable : SkNoncopyable {
+public:
+ AutoCGTable(CGFontRef font)
+ //Undocumented: the tag parameter in this call is expected in machine order and not BE order.
+ : fCFData(CGFontCopyTableForTag(font, SkSetFourByteTag(T::TAG0, T::TAG1, T::TAG2, T::TAG3)))
+ , fData(fCFData ? reinterpret_cast<const T*>(CFDataGetBytePtr(fCFData)) : NULL)
+ { }
+
+ const T* operator->() const { return fData; }
+
+private:
+ AutoCFRelease<CFDataRef> fCFData;
+public:
+ const T* fData;
};
// inline versions of these rect helpers
@@ -87,8 +134,8 @@ static CGFloat CGRectGetWidth_inline(const CGRect& rect) {
///////////////////////////////////////////////////////////////////////////////
-static void sk_memset_rect32(uint32_t* ptr, uint32_t value, size_t width,
- size_t height, size_t rowBytes) {
+static void sk_memset_rect32(uint32_t* ptr, uint32_t value,
+ size_t width, size_t height, size_t rowBytes) {
SkASSERT(width);
SkASSERT(width * sizeof(uint32_t) <= rowBytes);
@@ -131,21 +178,6 @@ static void sk_memset_rect32(uint32_t* ptr, uint32_t value, size_t width,
}
}
-// Potentially this should be made (1) public (2) optimized when width is small.
-// Also might want 16 and 32 bit version
-//
-#if 0 // UNUSED
-static void sk_memset_rect(void* ptr, U8CPU byte, size_t width, size_t height,
- size_t rowBytes) {
- uint8_t* dst = (uint8_t*)ptr;
- while (height) {
- memset(dst, byte, width);
- dst += rowBytes;
- height -= 1;
- }
-}
-#endif
-
#include <sys/utsname.h>
typedef uint32_t CGRGBPixel;
@@ -162,27 +194,17 @@ static unsigned CGRGBPixel_getAlpha(CGRGBPixel pixel) {
// cd ApplicationServices.framework/Frameworks/CoreGraphics.framework/
// nm CoreGraphics | grep CGContextSetShouldSubpixelQuantizeFonts
-#if !defined(MAC_OS_X_VERSION_10_6) || \
- MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6
- CG_EXTERN void CGContextSetAllowsFontSmoothing(CGContextRef context,
- bool allowsFontSmoothing);
- CG_EXTERN void CGContextSetAllowsFontSubpixelPositioning(
- CGContextRef context,
- bool allowsFontSubpixelPositioning);
- CG_EXTERN void CGContextSetShouldSubpixelPositionFonts(CGContextRef context,
- bool shouldSubpixelPositionFonts);
- CG_EXTERN void CGContextSetAllowsFontSubpixelQuantization(
- CGContextRef context,
- bool allowsFontSubpixelQuantization);
- CG_EXTERN void CGContextSetShouldSubpixelQuantizeFonts(
- CGContextRef context,
- bool shouldSubpixelQuantizeFonts);
+#if !defined(MAC_OS_X_VERSION_10_6) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6)
+CG_EXTERN void CGContextSetAllowsFontSmoothing(CGContextRef context, bool value);
+CG_EXTERN void CGContextSetAllowsFontSubpixelPositioning(CGContextRef context, bool value);
+CG_EXTERN void CGContextSetShouldSubpixelPositionFonts(CGContextRef context, bool value);
+CG_EXTERN void CGContextSetAllowsFontSubpixelQuantization(CGContextRef context, bool value);
+CG_EXTERN void CGContextSetShouldSubpixelQuantizeFonts(CGContextRef context, bool value);
#endif
-static const char FONT_DEFAULT_NAME[] = "Lucida Sans";
+static const char FONT_DEFAULT_NAME[] = "Lucida Sans";
-// see Source/WebKit/chromium/base/mac/mac_util.mm DarwinMajorVersionInternal
-// for original source
+// See Source/WebKit/chromium/base/mac/mac_util.mm DarwinMajorVersionInternal for original source.
static int readVersion() {
struct utsname info;
if (uname(&info) != 0) {
@@ -249,13 +271,14 @@ static SkScalar CGToScalar(CGFloat cgFloat) {
}
static CGAffineTransform MatrixToCGAffineTransform(const SkMatrix& matrix,
- float sx = 1, float sy = 1) {
- return CGAffineTransformMake(ScalarToCG(matrix[SkMatrix::kMScaleX]) * sx,
- -ScalarToCG(matrix[SkMatrix::kMSkewY]) * sy,
- -ScalarToCG(matrix[SkMatrix::kMSkewX]) * sx,
- ScalarToCG(matrix[SkMatrix::kMScaleY]) * sy,
- ScalarToCG(matrix[SkMatrix::kMTransX]) * sx,
- ScalarToCG(matrix[SkMatrix::kMTransY]) * sy);
+ SkScalar sx = SK_Scalar1,
+ SkScalar sy = SK_Scalar1) {
+ return CGAffineTransformMake( ScalarToCG(matrix[SkMatrix::kMScaleX] * sx),
+ -ScalarToCG(matrix[SkMatrix::kMSkewY] * sy),
+ -ScalarToCG(matrix[SkMatrix::kMSkewX] * sx),
+ ScalarToCG(matrix[SkMatrix::kMScaleY] * sy),
+ ScalarToCG(matrix[SkMatrix::kMTransX] * sx),
+ ScalarToCG(matrix[SkMatrix::kMTransY] * sy));
}
static SkScalar getFontScale(CGFontRef cgFont) {
@@ -265,8 +288,8 @@ static SkScalar getFontScale(CGFontRef cgFont) {
///////////////////////////////////////////////////////////////////////////////
-#define BITMAP_INFO_RGB (kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host)
-#define BITMAP_INFO_GRAY (kCGImageAlphaNone)
+#define BITMAP_INFO_RGB (kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host)
+#define BITMAP_INFO_GRAY (kCGImageAlphaNone)
/**
* There does not appear to be a publicly accessable API for determining if lcd
@@ -278,29 +301,26 @@ static bool supports_LCD() {
if (gSupportsLCD >= 0) {
return (bool) gSupportsLCD;
}
- int rgb = 0;
- CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
- CGContextRef cgContext = CGBitmapContextCreate(&rgb, 1, 1, 8, 4, colorspace,
- BITMAP_INFO_RGB);
+ uint32_t rgb = 0;
+ AutoCFRelease<CGColorSpaceRef> colorspace(CGColorSpaceCreateDeviceRGB());
+ AutoCFRelease<CGContextRef> cgContext(CGBitmapContextCreate(&rgb, 1, 1, 8, 4,
+ colorspace, BITMAP_INFO_RGB));
CGContextSelectFont(cgContext, "Helvetica", 16, kCGEncodingMacRoman);
CGContextSetShouldSmoothFonts(cgContext, true);
CGContextSetShouldAntialias(cgContext, true);
CGContextSetTextDrawingMode(cgContext, kCGTextFill);
- CGContextSetGrayFillColor( cgContext, 1, 1);
+ CGContextSetGrayFillColor(cgContext, 1, 1);
CGContextShowTextAtPoint(cgContext, -1, 0, "|", 1);
- CFSafeRelease(colorspace);
- CFSafeRelease(cgContext);
- int r = (rgb >> 16) & 0xFF;
- int g = (rgb >> 8) & 0xFF;
- int b = (rgb >> 0) & 0xFF;
- gSupportsLCD = r != g || r != b;
+ uint32_t r = (rgb >> 16) & 0xFF;
+ uint32_t g = (rgb >> 8) & 0xFF;
+ uint32_t b = (rgb >> 0) & 0xFF;
+ gSupportsLCD = (r != g || r != b);
return (bool) gSupportsLCD;
}
class Offscreen {
public:
Offscreen();
- ~Offscreen();
CGRGBPixel* getCG(const SkScalerContext_Mac& context, const SkGlyph& glyph,
CGGlyph glyphID, size_t* rowBytesPtr,
@@ -311,13 +331,13 @@ private:
kSize = 32 * 32 * sizeof(CGRGBPixel)
};
SkAutoSMalloc<kSize> fImageStorage;
- CGColorSpaceRef fRGBSpace;
+ AutoCFRelease<CGColorSpaceRef> fRGBSpace;
// cached state
- CGContextRef fCG;
- SkISize fSize;
- bool fDoAA;
- bool fDoLCD;
+ AutoCFRelease<CGContextRef> fCG;
+ SkISize fSize;
+ bool fDoAA;
+ bool fDoLCD;
static int RoundSize(int dimension) {
return SkNextPow2(dimension);
@@ -325,12 +345,7 @@ private:
};
Offscreen::Offscreen() : fRGBSpace(NULL), fCG(NULL) {
- fSize.set(0,0);
-}
-
-Offscreen::~Offscreen() {
- CFSafeRelease(fCG);
- CFSafeRelease(fRGBSpace);
+ fSize.set(0, 0);
}
///////////////////////////////////////////////////////////////////////////////
@@ -351,17 +366,6 @@ static SkTypeface::Style computeStyleBits(CTFontRef font, bool* isMonospace) {
return (SkTypeface::Style)style;
}
-class AutoCFDataRelease {
-public:
- AutoCFDataRelease(CFDataRef obj) : fObj(obj) {}
- const uint16_t* getShortPtr() {
- return fObj ? (const uint16_t*) CFDataGetBytePtr(fObj) : NULL;
- }
- ~AutoCFDataRelease() { CFSafeRelease(fObj); }
-private:
- CFDataRef fObj;
-};
-
static SkFontID CTFontRef_to_SkFontID(CTFontRef fontRef) {
SkFontID id = 0;
// CTFontGetPlatformFont and ATSFontRef are not supported on iOS, so we have to
@@ -376,11 +380,10 @@ static SkFontID CTFontRef_to_SkFontID(CTFontRef fontRef) {
#endif
// CTFontGetPlatformFont returns NULL if the font is local
// (e.g., was created by a CSS3 @font-face rule).
- CGFontRef cgFont = CTFontCopyGraphicsFont(fontRef, NULL);
- AutoCFDataRelease headRef(CGFontCopyTableForTag(cgFont, 'head'));
- const uint16_t* headData = headRef.getShortPtr();
- if (headData) {
- id = (SkFontID) (headData[4] | headData[5] << 16); // checksum
+ AutoCFRelease<CGFontRef> cgFont(CTFontCopyGraphicsFont(fontRef, NULL));
+ AutoCGTable<SkOTTableHead> headTable(cgFont);
+ if (headTable.fData) {
+ id = (SkFontID) headTable->checksumAdjustment;
id = (id & 0x3FFFFFFF) | 0x40000000; // make top two bits 01
}
// well-formed fonts have checksums, but as a last resort, use the pointer.
@@ -388,7 +391,6 @@ static SkFontID CTFontRef_to_SkFontID(CTFontRef fontRef) {
id = (SkFontID) (uintptr_t) fontRef;
id = (id & 0x3FFFFFFF) | 0x80000000; // make top two bits 10
}
- CGFontRelease(cgFont);
return id;
}
@@ -396,16 +398,15 @@ class SkTypeface_Mac : public SkTypeface {
public:
SkTypeface_Mac(SkTypeface::Style style, SkFontID fontID, bool isMonospace,
CTFontRef fontRef, const char name[])
- : SkTypeface(style, fontID, isMonospace) {
+ : SkTypeface(style, fontID, isMonospace)
+ , fName(name)
+ , fFontRef(fontRef) // caller has already called CFRetain for us
+ {
SkASSERT(fontRef);
- fFontRef = fontRef; // caller has already called CFRetain for us
- fName.set(name);
}
- virtual ~SkTypeface_Mac() { CFRelease(fFontRef); }
-
- SkString fName;
- CTFontRef fFontRef;
+ SkString fName;
+ AutoCFRelease<CTFontRef> fFontRef;
};
static CTFontRef typeface_to_fontref(const SkTypeface* face) {
@@ -422,69 +423,61 @@ static SkTypeface* NewFromFontRef(CTFontRef fontRef, const char name[]) {
return new SkTypeface_Mac(style, fontID, isMonospace, fontRef, name);
}
-static SkTypeface* NewFromName(const char familyName[],
- SkTypeface::Style theStyle) {
- CFMutableDictionaryRef cfAttributes, cfTraits;
- CFNumberRef cfFontTraits;
- CTFontSymbolicTraits ctFontTraits;
- CTFontDescriptorRef ctFontDesc;
- CFStringRef cfFontName;
- CTFontRef ctFont;
-
-
- // Get the state we need
- ctFontDesc = NULL;
- ctFont = NULL;
- ctFontTraits = 0;
+static SkTypeface* NewFromName(const char familyName[], SkTypeface::Style theStyle) {
+ CTFontRef ctFont = NULL;
+ CTFontSymbolicTraits ctFontTraits = 0;
if (theStyle & SkTypeface::kBold) {
ctFontTraits |= kCTFontBoldTrait;
}
-
if (theStyle & SkTypeface::kItalic) {
ctFontTraits |= kCTFontItalicTrait;
}
// Create the font info
- cfFontName = CFStringCreateWithCString(NULL, familyName, kCFStringEncodingUTF8);
- cfFontTraits = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &ctFontTraits);
- cfAttributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
- cfTraits = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ AutoCFRelease<CFStringRef> cfFontName(
+ CFStringCreateWithCString(NULL, familyName, kCFStringEncodingUTF8));
+ AutoCFRelease<CFNumberRef> cfFontTraits(
+ CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &ctFontTraits));
+
+ AutoCFRelease<CFMutableDictionaryRef> cfAttributes(
+ CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks));
+
+ AutoCFRelease<CFMutableDictionaryRef> cfTraits(
+ CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks));
// Create the font
if (cfFontName != NULL && cfFontTraits != NULL && cfAttributes != NULL && cfTraits != NULL) {
CFDictionaryAddValue(cfTraits, kCTFontSymbolicTrait, cfFontTraits);
CFDictionaryAddValue(cfAttributes, kCTFontFamilyNameAttribute, cfFontName);
- CFDictionaryAddValue(cfAttributes, kCTFontTraitsAttribute, cfTraits);
+ CFDictionaryAddValue(cfAttributes, kCTFontTraitsAttribute, cfTraits);
+
+ AutoCFRelease<CTFontDescriptorRef> ctFontDesc(
+ CTFontDescriptorCreateWithAttributes(cfAttributes));
- ctFontDesc = CTFontDescriptorCreateWithAttributes(cfAttributes);
if (ctFontDesc != NULL) {
if (isLeopard()) {
// CTFontCreateWithFontDescriptor on Leopard ignores the name
- CTFontRef ctNamed = CTFontCreateWithName(cfFontName, 1, NULL);
- ctFont = CTFontCreateCopyWithAttributes(ctNamed, 1, NULL,
- ctFontDesc);
- CFSafeRelease(ctNamed);
+ AutoCFRelease<CTFontRef> ctNamed(CTFontCreateWithName(cfFontName, 1, NULL));
+ ctFont = CTFontCreateCopyWithAttributes(ctNamed, 1, NULL, ctFontDesc);
} else {
ctFont = CTFontCreateWithFontDescriptor(ctFontDesc, 0, NULL);
}
}
}
- CFSafeRelease(cfFontName);
- CFSafeRelease(cfFontTraits);
- CFSafeRelease(cfAttributes);
- CFSafeRelease(cfTraits);
- CFSafeRelease(ctFontDesc);
-
return ctFont ? NewFromFontRef(ctFont, familyName) : NULL;
}
static CTFontRef GetFontRefFromFontID(SkFontID fontID) {
SkTypeface_Mac* face = reinterpret_cast<SkTypeface_Mac*>(SkTypefaceCache::FindByID(fontID));
- return face ? face->fFontRef : 0;
+ return face ? face->fFontRef.get() : NULL;
}
static SkTypeface* GetDefaultFace() {
@@ -505,7 +498,7 @@ static SkTypeface* GetDefaultFace() {
extern CTFontRef SkTypeface_GetCTFontRef(const SkTypeface* face);
CTFontRef SkTypeface_GetCTFontRef(const SkTypeface* face) {
const SkTypeface_Mac* macface = (const SkTypeface_Mac*)face;
- return macface ? macface->fFontRef : NULL;
+ return macface ? macface->fFontRef.get() : NULL;
}
/* This function is visible on the outside. It first searches the cache, and if
@@ -608,52 +601,49 @@ struct GlyphRect {
class SkScalerContext_Mac : public SkScalerContext {
public:
- SkScalerContext_Mac(const SkDescriptor* desc);
- virtual ~SkScalerContext_Mac(void);
+ SkScalerContext_Mac(const SkDescriptor* desc);
+ virtual ~SkScalerContext_Mac(void);
protected:
- unsigned generateGlyphCount(void) SK_OVERRIDE;
- uint16_t generateCharToGlyph(SkUnichar uni) SK_OVERRIDE;
- void generateAdvance(SkGlyph* glyph) SK_OVERRIDE;
- void generateMetrics(SkGlyph* glyph) SK_OVERRIDE;
- void generateImage(const SkGlyph& glyph, SkMaskGamma::PreBlend* maskPreBlend) SK_OVERRIDE;
- void generatePath(const SkGlyph& glyph, SkPath* path) SK_OVERRIDE;
- void generateFontMetrics(SkPaint::FontMetrics* mX, SkPaint::FontMetrics* mY) SK_OVERRIDE;
-
+ unsigned generateGlyphCount(void) SK_OVERRIDE;
+ uint16_t generateCharToGlyph(SkUnichar uni) SK_OVERRIDE;
+ void generateAdvance(SkGlyph* glyph) SK_OVERRIDE;
+ void generateMetrics(SkGlyph* glyph) SK_OVERRIDE;
+ void generateImage(const SkGlyph& glyph) SK_OVERRIDE;
+ void generatePath(const SkGlyph& glyph, SkPath* path) SK_OVERRIDE;
+ void generateFontMetrics(SkPaint::FontMetrics* mX, SkPaint::FontMetrics* mY) SK_OVERRIDE;
private:
- static void CTPathElement(void *info, const CGPathElement *element);
- uint16_t getAdjustStart();
- void getVerticalOffset(CGGlyph glyphID, SkIPoint* offset) const;
- bool generateBBoxes();
-
-private:
- CGAffineTransform fTransform;
- SkMatrix fUnitMatrix; // without font size
- SkMatrix fVerticalMatrix; // unit rotated
- SkMatrix fMatrix; // with font size
- SkMatrix fAdjustBadMatrix; // lion-specific fix
- Offscreen fOffscreen;
- CTFontRef fCTFont;
- CTFontRef fCTVerticalFont; // for vertical advance
- CGFontRef fCGFont;
- GlyphRect* fAdjustBad;
- uint16_t fAdjustStart;
- uint16_t fGlyphCount;
- bool fGeneratedBBoxes;
- bool fDoSubPosition;
- bool fVertical;
-
- friend class Offscreen;
+ static void CTPathElement(void *info, const CGPathElement *element);
+ uint16_t getFBoundingBoxesGlyphOffset();
+ void getVerticalOffset(CGGlyph glyphID, SkIPoint* offset) const;
+ bool generateBBoxes();
+
+ CGAffineTransform fTransform;
+ SkMatrix fUnitMatrix; // without font size
+ SkMatrix fVerticalMatrix; // unit rotated
+ SkMatrix fMatrix; // with font size
+ SkMatrix fFBoundingBoxesMatrix; // lion-specific fix
+ Offscreen fOffscreen;
+ AutoCFRelease<CTFontRef> fCTFont;
+ AutoCFRelease<CTFontRef> fCTVerticalFont; // for vertical advance
+ AutoCFRelease<CGFontRef> fCGFont;
+ GlyphRect* fFBoundingBoxes;
+ uint16_t fFBoundingBoxesGlyphOffset;
+ uint16_t fGlyphCount;
+ bool fGeneratedFBoundingBoxes;
+ bool fDoSubPosition;
+ bool fVertical;
+
+ friend class Offscreen;
};
SkScalerContext_Mac::SkScalerContext_Mac(const SkDescriptor* desc)
: SkScalerContext(desc)
- , fCTVerticalFont(NULL)
- , fAdjustBad(NULL)
- , fAdjustStart(0)
- , fGeneratedBBoxes(false)
+ , fFBoundingBoxes(NULL)
+ , fFBoundingBoxesGlyphOffset(0)
+ , fGeneratedFBoundingBoxes(false)
{
CTFontRef ctFont = GetFontRefFromFontID(fRec.fFontID);
CFIndex numGlyphs = CTFontGetGlyphCount(ctFont);
@@ -684,31 +674,26 @@ SkScalerContext_Mac::SkScalerContext_Mac(const SkDescriptor* desc)
}
flip(&fUnitMatrix); // flip to fix up bounds later
fVertical = SkToBool(fRec.fFlags & kVertical_Flag);
- CTFontDescriptorRef ctFontDesc = NULL;
+ AutoCFRelease<CTFontDescriptorRef> ctFontDesc;
if (fVertical) {
- CFMutableDictionaryRef cfAttributes = CFDictionaryCreateMutable(
- kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
- &kCFTypeDictionaryValueCallBacks);
+ AutoCFRelease<CFMutableDictionaryRef> cfAttributes(CFDictionaryCreateMutable(
+ kCFAllocatorDefault, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks));
if (cfAttributes) {
CTFontOrientation ctOrientation = kCTFontVerticalOrientation;
- CFNumberRef cfVertical = CFNumberCreate(kCFAllocatorDefault,
- kCFNumberSInt32Type, &ctOrientation);
- CFDictionaryAddValue(cfAttributes, kCTFontOrientationAttribute,
- cfVertical);
- CFSafeRelease(cfVertical);
+ AutoCFRelease<CFNumberRef> cfVertical(CFNumberCreate(
+ kCFAllocatorDefault, kCFNumberSInt32Type, &ctOrientation));
+ CFDictionaryAddValue(cfAttributes, kCTFontOrientationAttribute, cfVertical);
ctFontDesc = CTFontDescriptorCreateWithAttributes(cfAttributes);
- CFRelease(cfAttributes);
}
}
- fCTFont = CTFontCreateCopyWithAttributes(ctFont, unitFontSize, &transform,
- ctFontDesc);
- CFSafeRelease(ctFontDesc);
+ fCTFont = CTFontCreateCopyWithAttributes(ctFont, unitFontSize, &transform, ctFontDesc);
fCGFont = CTFontCopyGraphicsFont(fCTFont, NULL);
if (fVertical) {
CGAffineTransform rotateLeft = CGAffineTransformMake(0, -1, 1, 0, 0, 0);
transform = CGAffineTransformConcat(rotateLeft, transform);
- fCTVerticalFont = CTFontCreateCopyWithAttributes(ctFont, unitFontSize,
- &transform, NULL);
+ fCTVerticalFont = CTFontCreateCopyWithAttributes(ctFont, unitFontSize, &transform, NULL);
fVerticalMatrix = fUnitMatrix;
if (isSnowLeopard()) {
SkScalar scale = SkScalarMul(fRec.fTextSize, getFontScale(fCGFont));
@@ -723,10 +708,7 @@ SkScalerContext_Mac::SkScalerContext_Mac(const SkDescriptor* desc)
}
SkScalerContext_Mac::~SkScalerContext_Mac() {
- delete[] fAdjustBad;
- CFSafeRelease(fCTFont);
- CFSafeRelease(fCTVerticalFont);
- CFSafeRelease(fCGFont);
+ delete[] fFBoundingBoxes;
}
CGRGBPixel* Offscreen::getCG(const SkScalerContext_Mac& context, const SkGlyph& glyph,
@@ -756,7 +738,6 @@ CGRGBPixel* Offscreen::getCG(const SkScalerContext_Mac& context, const SkGlyph&
size_t rowBytes = fSize.fWidth * sizeof(CGRGBPixel);
if (!fCG || fSize.fWidth < glyph.fWidth || fSize.fHeight < glyph.fHeight) {
- CFSafeRelease(fCG);
if (fSize.fWidth < glyph.fWidth) {
fSize.fWidth = RoundSize(glyph.fWidth);
}
@@ -846,48 +827,16 @@ void SkScalerContext_Mac::getVerticalOffset(CGGlyph glyphID, SkIPoint* offset) c
offset->fY = SkScalarRound(floatOffset.fY);
}
-/* from http://developer.apple.com/fonts/TTRefMan/RM06/Chap6loca.html
- * There are two versions of this table, the short and the long. The version
- * used is specified in the Font Header ('head') table in the indexToLocFormat
- * field. The choice of long or short offsets is dependent on the maximum
- * possible offset distance.
- *
- * 'loca' short version: The actual local offset divided by 2 is stored.
- * 'loca' long version: The actual local offset is stored.
- *
- * The result is a offset into a table of 2 byte (16 bit) entries.
- */
-static uint32_t getLocaTableEntry(const uint16_t*& locaPtr, int locaFormat) {
- uint32_t data = SkEndian_SwapBE16(*locaPtr++); // short
- if (locaFormat) {
- data = data << 15 | SkEndian_SwapBE16(*locaPtr++) >> 1; // long
- }
- return data;
-}
-
-// see http://developer.apple.com/fonts/TTRefMan/RM06/Chap6hhea.html
-static uint16_t getNumLongMetrics(const uint16_t* hheaData) {
- const int kNumOfLongHorMetrics = 17;
- return SkEndian_SwapBE16(hheaData[kNumOfLongHorMetrics]);
-}
-
-// see http://developer.apple.com/fonts/TTRefMan/RM06/Chap6head.html
-static int getLocaFormat(const uint16_t* headData) {
- const int kIndexToLocFormat = 25;
- return SkEndian_SwapBE16(headData[kIndexToLocFormat]);
-}
-
-uint16_t SkScalerContext_Mac::getAdjustStart() {
- if (fAdjustStart) {
- return fAdjustStart;
+uint16_t SkScalerContext_Mac::getFBoundingBoxesGlyphOffset() {
+ if (fFBoundingBoxesGlyphOffset) {
+ return fFBoundingBoxesGlyphOffset;
}
- fAdjustStart = fGlyphCount; // fallback for all fonts
- AutoCFDataRelease hheaRef(CGFontCopyTableForTag(fCGFont, 'hhea'));
- const uint16_t* hheaData = hheaRef.getShortPtr();
- if (hheaData) {
- fAdjustStart = getNumLongMetrics(hheaData);
+ fFBoundingBoxesGlyphOffset = fGlyphCount; // fallback for all fonts
+ AutoCGTable<SkOTTableHorizontalHeader> hheaTable(fCGFont);
+ if (hheaTable.fData) {
+ fFBoundingBoxesGlyphOffset = SkEndian_SwapBE16(hheaTable->numberOfHMetrics);
}
- return fAdjustStart;
+ return fFBoundingBoxesGlyphOffset;
}
/*
@@ -896,78 +845,73 @@ uint16_t SkScalerContext_Mac::getAdjustStart() {
* glyph count. This workaround reads the glyph bounds from the font directly.
*
* The table is computed only if the font is a TrueType font, if the glyph
- * value is >= fAdjustStart. (called only if fAdjustStart < fGlyphCount).
+ * value is >= fFBoundingBoxesGlyphOffset. (called only if fFBoundingBoxesGlyphOffset < fGlyphCount).
*
- * TODO: A future optimization will compute fAdjustBad once per CGFont, and
- * compute fAdjustBadMatrix once per font context.
+ * TODO: A future optimization will compute fFBoundingBoxes once per CGFont, and
+ * compute fFBoundingBoxesMatrix once per font context.
*/
bool SkScalerContext_Mac::generateBBoxes() {
- if (fGeneratedBBoxes) {
- return NULL != fAdjustBad;
+ if (fGeneratedFBoundingBoxes) {
+ return NULL != fFBoundingBoxes;
}
- fGeneratedBBoxes = true;
- AutoCFDataRelease headRef(CGFontCopyTableForTag(fCGFont, 'head'));
- const uint16_t* headData = headRef.getShortPtr();
- if (!headData) {
+ fGeneratedFBoundingBoxes = true;
+
+ AutoCGTable<SkOTTableHead> headTable(fCGFont);
+ if (!headTable.fData) {
return false;
}
- AutoCFDataRelease locaRef(CGFontCopyTableForTag(fCGFont, 'loca'));
- const uint16_t* locaData = locaRef.getShortPtr();
- if (!locaData) {
+
+ AutoCGTable<SkOTTableIndexToLocation> locaTable(fCGFont);
+ if (!locaTable.fData) {
return false;
}
- AutoCFDataRelease glyfRef(CGFontCopyTableForTag(fCGFont, 'glyf'));
- const uint16_t* glyfData = glyfRef.getShortPtr();
- if (!glyfData) {
+
+ AutoCGTable<SkOTTableGlyph> glyfTable(fCGFont);
+ if (!glyfTable.fData) {
return false;
}
- CFIndex entries = fGlyphCount - fAdjustStart;
- fAdjustBad = new GlyphRect[entries];
- int locaFormat = getLocaFormat(headData);
- const uint16_t* locaPtr = &locaData[fAdjustStart << locaFormat];
- uint32_t last = getLocaTableEntry(locaPtr, locaFormat);
- for (CFIndex index = 0; index < entries; ++index) {
- uint32_t offset = getLocaTableEntry(locaPtr, locaFormat);
- GlyphRect& rect = fAdjustBad[index];
- if (offset != last) {
- rect.fMinX = SkEndian_SwapBE16(glyfData[last + 1]);
- rect.fMinY = SkEndian_SwapBE16(glyfData[last + 2]);
- rect.fMaxX = SkEndian_SwapBE16(glyfData[last + 3]);
- rect.fMaxY = SkEndian_SwapBE16(glyfData[last + 4]);
- } else {
- sk_bzero(&rect, sizeof(GlyphRect));
- }
- last = offset;
+
+ uint16_t entries = fGlyphCount - fFBoundingBoxesGlyphOffset;
+ fFBoundingBoxes = new GlyphRect[entries];
+
+ SkOTTableHead::IndexToLocFormat locaFormat = headTable->indexToLocFormat;
+ SkOTTableGlyph::Iterator glyphDataIter(*glyfTable.fData, *locaTable.fData, locaFormat);
+ glyphDataIter.advance(fFBoundingBoxesGlyphOffset);
+ for (uint16_t boundingBoxesIndex = 0; boundingBoxesIndex < entries; ++boundingBoxesIndex) {
+ const SkOTTableGlyphData* glyphData = glyphDataIter.next();
+ GlyphRect& rect = fFBoundingBoxes[boundingBoxesIndex];
+ rect.fMinX = SkEndian_SwapBE16(glyphData->xMin);
+ rect.fMinY = SkEndian_SwapBE16(glyphData->yMin);
+ rect.fMaxX = SkEndian_SwapBE16(glyphData->xMax);
+ rect.fMaxY = SkEndian_SwapBE16(glyphData->yMax);
}
- fAdjustBadMatrix = fMatrix;
- flip(&fAdjustBadMatrix);
+ fFBoundingBoxesMatrix = fMatrix;
+ flip(&fFBoundingBoxesMatrix);
SkScalar fontScale = getFontScale(fCGFont);
- fAdjustBadMatrix.preScale(fontScale, fontScale);
+ fFBoundingBoxesMatrix.preScale(fontScale, fontScale);
return true;
}
-unsigned SkScalerContext_Mac::generateGlyphCount(void)
-{
- return(fGlyphCount);
+unsigned SkScalerContext_Mac::generateGlyphCount(void) {
+ return fGlyphCount;
}
-uint16_t SkScalerContext_Mac::generateCharToGlyph(SkUnichar uni)
-{ CGGlyph cgGlyph;
+uint16_t SkScalerContext_Mac::generateCharToGlyph(SkUnichar uni) {
+ CGGlyph cgGlyph;
UniChar theChar;
-
// Validate our parameters and state
- SkASSERT(uni <= 0x0000FFFF);
+ SkASSERT(uni <= 0x0000FFFF);
SkASSERT(sizeof(CGGlyph) <= sizeof(uint16_t));
-
// Get the glyph
theChar = (UniChar) uni;
- if (!CTFontGetGlyphsForCharacters(fCTFont, &theChar, &cgGlyph, 1))
+ if (!CTFontGetGlyphsForCharacters(fCTFont, &theChar, &cgGlyph, 1)) {
cgGlyph = 0;
+ }
- return(cgGlyph);
+ return cgGlyph;
}
void SkScalerContext_Mac::generateAdvance(SkGlyph* glyph) {
@@ -975,32 +919,31 @@ void SkScalerContext_Mac::generateAdvance(SkGlyph* glyph) {
}
void SkScalerContext_Mac::generateMetrics(SkGlyph* glyph) {
- CGSize theAdvance;
- CGRect theBounds;
- CGGlyph cgGlyph;
+ CGSize advance;
+ CGRect bounds;
+ CGGlyph cgGlyph;
// Get the state we need
cgGlyph = (CGGlyph) glyph->getGlyphID(fBaseGlyphCount);
if (fVertical) {
if (!isSnowLeopard()) {
- // Lion and Leopard respect the vertical font metrics.
- CTFontGetBoundingRectsForGlyphs(fCTVerticalFont,
- kCTFontVerticalOrientation,
- &cgGlyph, &theBounds, 1);
+ // Lion and Leopard respect the vertical font metrics.
+ CTFontGetBoundingRectsForGlyphs(fCTVerticalFont, kCTFontVerticalOrientation,
+ &cgGlyph, &bounds, 1);
} else {
- // Snow Leopard and earlier respect the vertical font metrics for
- // advances, but not bounds, so use the default box and adjust it below.
+ // Snow Leopard and earlier respect the vertical font metrics for
+ // advances, but not bounds, so use the default box and adjust it below.
CTFontGetBoundingRectsForGlyphs(fCTFont, kCTFontDefaultOrientation,
- &cgGlyph, &theBounds, 1);
+ &cgGlyph, &bounds, 1);
}
CTFontGetAdvancesForGlyphs(fCTVerticalFont, kCTFontVerticalOrientation,
- &cgGlyph, &theAdvance, 1);
+ &cgGlyph, &advance, 1);
} else {
CTFontGetBoundingRectsForGlyphs(fCTFont, kCTFontDefaultOrientation,
- &cgGlyph, &theBounds, 1);
+ &cgGlyph, &bounds, 1);
CTFontGetAdvancesForGlyphs(fCTFont, kCTFontDefaultOrientation,
- &cgGlyph, &theAdvance, 1);
+ &cgGlyph, &advance, 1);
}
// BUG?
@@ -1008,21 +951,18 @@ void SkScalerContext_Mac::generateMetrics(SkGlyph* glyph) {
// it should be empty. So, if we see a zero-advance, we check if it has an
// empty path or not, and if so, we jam the bounds to 0. Hopefully a zero-advance
// is rare, so we won't incur a big performance cost for this extra check.
- if (0 == theAdvance.width && 0 == theAdvance.height) {
- CGPathRef path = CTFontCreatePathForGlyph(fCTFont, cgGlyph, NULL);
+ if (0 == advance.width && 0 == advance.height) {
+ AutoCFRelease<CGPathRef> path(CTFontCreatePathForGlyph(fCTFont, cgGlyph, NULL));
if (NULL == path || CGPathIsEmpty(path)) {
- theBounds = CGRectMake(0, 0, 0, 0);
- }
- if (path) {
- CGPathRelease(path);
+ bounds = CGRectMake(0, 0, 0, 0);
}
}
glyph->zeroMetrics();
- glyph->fAdvanceX = SkFloatToFixed_Check(theAdvance.width);
- glyph->fAdvanceY = -SkFloatToFixed_Check(theAdvance.height);
+ glyph->fAdvanceX = SkFloatToFixed_Check(advance.width);
+ glyph->fAdvanceY = -SkFloatToFixed_Check(advance.height);
- if (CGRectIsEmpty_inline(theBounds)) {
+ if (CGRectIsEmpty_inline(bounds)) {
return;
}
@@ -1033,13 +973,13 @@ void SkScalerContext_Mac::generateMetrics(SkGlyph* glyph) {
// FIXME (Leopard): If the font has synthetic italic (e.g., matrix skew)
// and the font is vertical, the bounds need to be recomputed.
SkRect glyphBounds = SkRect::MakeXYWH(
- theBounds.origin.x, theBounds.origin.y,
- theBounds.size.width, theBounds.size.height);
+ bounds.origin.x, bounds.origin.y,
+ bounds.size.width, bounds.size.height);
fUnitMatrix.mapRect(&glyphBounds);
- theBounds.origin.x = glyphBounds.fLeft;
- theBounds.origin.y = glyphBounds.fTop;
- theBounds.size.width = glyphBounds.width();
- theBounds.size.height = glyphBounds.height();
+ bounds.origin.x = glyphBounds.fLeft;
+ bounds.origin.y = glyphBounds.fTop;
+ bounds.size.width = glyphBounds.width();
+ bounds.size.height = glyphBounds.height();
}
// Adjust the bounds
//
@@ -1047,34 +987,33 @@ void SkScalerContext_Mac::generateMetrics(SkGlyph* glyph) {
// to transform the bounding box ourselves.
//
// The bounds are also expanded by 1 pixel, to give CG room for anti-aliasing.
- CGRectInset_inline(&theBounds, -1, -1);
+ CGRectInset_inline(&bounds, -1, -1);
// Get the metrics
bool lionAdjustedMetrics = false;
if (isLion() || isMountainLion()) {
- if (cgGlyph < fGlyphCount && cgGlyph >= getAdjustStart()
- && generateBBoxes()) {
+ if (cgGlyph < fGlyphCount && cgGlyph >= getFBoundingBoxesGlyphOffset() && generateBBoxes()){
lionAdjustedMetrics = true;
SkRect adjust;
- const GlyphRect& gRect = fAdjustBad[cgGlyph - fAdjustStart];
+ const GlyphRect& gRect = fFBoundingBoxes[cgGlyph - fFBoundingBoxesGlyphOffset];
adjust.set(gRect.fMinX, gRect.fMinY, gRect.fMaxX, gRect.fMaxY);
- fAdjustBadMatrix.mapRect(&adjust);
- theBounds.origin.x = SkScalarToFloat(adjust.fLeft) - 1;
- theBounds.origin.y = SkScalarToFloat(adjust.fTop) - 1;
+ fFBoundingBoxesMatrix.mapRect(&adjust);
+ bounds.origin.x = SkScalarToFloat(adjust.fLeft) - 1;
+ bounds.origin.y = SkScalarToFloat(adjust.fTop) - 1;
}
// Lion returns fractions in the bounds
- glyph->fWidth = SkToU16(sk_float_ceil2int(theBounds.size.width));
- glyph->fHeight = SkToU16(sk_float_ceil2int(theBounds.size.height));
+ glyph->fWidth = SkToU16(sk_float_ceil2int(bounds.size.width));
+ glyph->fHeight = SkToU16(sk_float_ceil2int(bounds.size.height));
} else {
- glyph->fWidth = SkToU16(sk_float_round2int(theBounds.size.width));
- glyph->fHeight = SkToU16(sk_float_round2int(theBounds.size.height));
+ glyph->fWidth = SkToU16(sk_float_round2int(bounds.size.width));
+ glyph->fHeight = SkToU16(sk_float_round2int(bounds.size.height));
}
- glyph->fTop = SkToS16(-sk_float_round2int(CGRectGetMaxY_inline(theBounds)));
- glyph->fLeft = SkToS16(sk_float_round2int(CGRectGetMinX_inline(theBounds)));
+ glyph->fTop = SkToS16(-sk_float_round2int(CGRectGetMaxY_inline(bounds)));
+ glyph->fLeft = SkToS16(sk_float_round2int(CGRectGetMinX_inline(bounds)));
SkIPoint offset;
if (fVertical && (isSnowLeopard() || lionAdjustedMetrics)) {
- // SnowLeopard doesn't respect vertical metrics, so compute them manually.
- // Also compute them for Lion when the metrics were computed by hand.
+ // SnowLeopard doesn't respect vertical metrics, so compute them manually.
+ // Also compute them for Lion when the metrics were computed by hand.
getVerticalOffset(cgGlyph, &offset);
glyph->fLeft += offset.fX;
glyph->fTop += offset.fY;
@@ -1198,7 +1137,7 @@ template <typename T> T* SkTAddByteOffset(T* ptr, size_t byteOffset) {
return (T*)((char*)ptr + byteOffset);
}
-void SkScalerContext_Mac::generateImage(const SkGlyph& glyph, SkMaskGamma::PreBlend* maskPreBlend) {
+void SkScalerContext_Mac::generateImage(const SkGlyph& glyph) {
CGGlyph cgGlyph = (CGGlyph) glyph.getGlyphID(fBaseGlyphCount);
// FIXME: lcd smoothed un-hinted rasterization unsupported.
@@ -1237,37 +1176,31 @@ void SkScalerContext_Mac::generateImage(const SkGlyph& glyph, SkMaskGamma::PreBl
}
}
- // Must be careful not to use these if maskPreBlend == NULL
- const uint8_t* tableR = NULL;
- const uint8_t* tableG = NULL;
- const uint8_t* tableB = NULL;
- if (maskPreBlend) {
- tableR = maskPreBlend->fR;
- tableG = maskPreBlend->fG;
- tableB = maskPreBlend->fB;
- }
-
// Convert glyph to mask
switch (glyph.fMaskFormat) {
case SkMask::kLCD32_Format: {
- if (maskPreBlend) {
- rgb_to_lcd32<true>(cgPixels, cgRowBytes, glyph, tableR, tableG, tableB);
+ if (fPreBlend.isApplicable()) {
+ rgb_to_lcd32<true>(cgPixels, cgRowBytes, glyph,
+ fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
} else {
- rgb_to_lcd32<false>(cgPixels, cgRowBytes, glyph, tableR, tableG, tableB);
+ rgb_to_lcd32<false>(cgPixels, cgRowBytes, glyph,
+ fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
}
} break;
case SkMask::kLCD16_Format: {
- if (maskPreBlend) {
- rgb_to_lcd16<true>(cgPixels, cgRowBytes, glyph, tableR, tableG, tableB);
+ if (fPreBlend.isApplicable()) {
+ rgb_to_lcd16<true>(cgPixels, cgRowBytes, glyph,
+ fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
} else {
- rgb_to_lcd16<false>(cgPixels, cgRowBytes, glyph, tableR, tableG, tableB);
+ rgb_to_lcd16<false>(cgPixels, cgRowBytes, glyph,
+ fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
}
} break;
case SkMask::kA8_Format: {
- if (maskPreBlend) {
- rgb_to_a8<true>(cgPixels, cgRowBytes, glyph, tableG);
+ if (fPreBlend.isApplicable()) {
+ rgb_to_a8<true>(cgPixels, cgRowBytes, glyph, fPreBlend.fG);
} else {
- rgb_to_a8<false>(cgPixels, cgRowBytes, glyph, tableG);
+ rgb_to_a8<false>(cgPixels, cgRowBytes, glyph, fPreBlend.fG);
}
} break;
case SkMask::kBW_Format: {
@@ -1291,12 +1224,12 @@ void SkScalerContext_Mac::generateImage(const SkGlyph& glyph, SkMaskGamma::PreBl
* seems sufficient, and possibly even correct, to allow the hinted outline
* to be subpixel positioned.
*/
-#define kScaleForSubPixelPositionHinting 4
+#define kScaleForSubPixelPositionHinting (4.0f)
void SkScalerContext_Mac::generatePath(const SkGlyph& glyph, SkPath* path) {
CTFontRef font = fCTFont;
- float scaleX = 1;
- float scaleY = 1;
+ SkScalar scaleX = SK_Scalar1;
+ SkScalar scaleY = SK_Scalar1;
/*
* For subpixel positioning, we want to return an unhinted outline, so it
@@ -1312,14 +1245,14 @@ void SkScalerContext_Mac::generatePath(const SkGlyph& glyph, SkPath* path) {
fRec.getSingleMatrix(&m);
// start out by assuming that we want no hining in X and Y
- scaleX = scaleY = kScaleForSubPixelPositionHinting;
+ scaleX = scaleY = SkFloatToScalar(kScaleForSubPixelPositionHinting);
// now see if we need to restore hinting for axis-aligned baselines
switch (SkComputeAxisAlignmentForHText(m)) {
case kX_SkAxisAlignment:
- scaleY = 1; // want hinting in the Y direction
+ scaleY = SK_Scalar1; // want hinting in the Y direction
break;
case kY_SkAxisAlignment:
- scaleX = 1; // want hinting in the X direction
+ scaleX = SK_Scalar1; // want hinting in the X direction
break;
default:
break;
@@ -1330,21 +1263,20 @@ void SkScalerContext_Mac::generatePath(const SkGlyph& glyph, SkPath* path) {
font = CTFontCreateCopyWithAttributes(fCTFont, 1, &xform, NULL);
}
- CGGlyph cgGlyph = (CGGlyph)glyph.getGlyphID(fBaseGlyphCount);
- CGPathRef cgPath = CTFontCreatePathForGlyph(font, cgGlyph, NULL);
+ CGGlyph cgGlyph = (CGGlyph)glyph.getGlyphID(fBaseGlyphCount);
+ AutoCFRelease<CGPathRef> cgPath(CTFontCreatePathForGlyph(font, cgGlyph, NULL));
path->reset();
if (cgPath != NULL) {
CGPathApply(cgPath, path, SkScalerContext_Mac::CTPathElement);
- CFRelease(cgPath);
}
if (fRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag) {
SkMatrix m;
- m.setScale(SkFloatToScalar(1 / scaleX), SkFloatToScalar(1 / scaleY));
+ m.setScale(SkScalarInvert(scaleX), SkScalarInvert(scaleY));
path->transform(m);
// balance the call to CTFontCreateCopyWithAttributes
- CFRelease(font);
+ CFSafeRelease(font);
}
if (fRec.fFlags & SkScalerContext::kVertical_Flag) {
SkIPoint offset;
@@ -1376,23 +1308,22 @@ void SkScalerContext_Mac::generateFontMetrics(SkPaint::FontMetrics* mx,
}
}
-void SkScalerContext_Mac::CTPathElement(void *info, const CGPathElement *element)
-{ SkPath *skPath = (SkPath *) info;
-
+void SkScalerContext_Mac::CTPathElement(void *info, const CGPathElement *element) {
+ SkPath* skPath = (SkPath*)info;
// Process the path element
switch (element->type) {
case kCGPathElementMoveToPoint:
- skPath->moveTo( element->points[0].x, -element->points[0].y);
+ skPath->moveTo(element->points[0].x, -element->points[0].y);
break;
case kCGPathElementAddLineToPoint:
- skPath->lineTo( element->points[0].x, -element->points[0].y);
+ skPath->lineTo(element->points[0].x, -element->points[0].y);
break;
case kCGPathElementAddQuadCurveToPoint:
- skPath->quadTo( element->points[0].x, -element->points[0].y,
- element->points[1].x, -element->points[1].y);
+ skPath->quadTo(element->points[0].x, -element->points[0].y,
+ element->points[1].x, -element->points[1].y);
break;
case kCGPathElementAddCurveToPoint:
@@ -1417,47 +1348,35 @@ void SkScalerContext_Mac::CTPathElement(void *info, const CGPathElement *element
// Returns NULL on failure
// Call must still manage its ownership of provider
static SkTypeface* create_from_dataProvider(CGDataProviderRef provider) {
- CGFontRef cg = CGFontCreateWithDataProvider(provider);
+ AutoCFRelease<CGFontRef> cg(CGFontCreateWithDataProvider(provider));
if (NULL == cg) {
return NULL;
}
CTFontRef ct = CTFontCreateWithGraphicsFont(cg, 0, NULL, NULL);
- CGFontRelease(cg);
return cg ? SkCreateTypefaceFromCTFont(ct) : NULL;
}
-class AutoCGDataProviderRelease : SkNoncopyable {
-public:
- AutoCGDataProviderRelease(CGDataProviderRef provider) : fProvider(provider) {}
- ~AutoCGDataProviderRelease() { CGDataProviderRelease(fProvider); }
-
-private:
- CGDataProviderRef fProvider;
-};
-
SkTypeface* SkFontHost::CreateTypefaceFromStream(SkStream* stream) {
- CGDataProviderRef provider = SkCreateDataProviderFromStream(stream);
+ AutoCFRelease<CGDataProviderRef> provider(SkCreateDataProviderFromStream(stream));
if (NULL == provider) {
return NULL;
}
- AutoCGDataProviderRelease ar(provider);
return create_from_dataProvider(provider);
}
SkTypeface* SkFontHost::CreateTypefaceFromFile(const char path[]) {
- CGDataProviderRef provider = CGDataProviderCreateWithFilename(path);
+ AutoCFRelease<CGDataProviderRef> provider(CGDataProviderCreateWithFilename(path));
if (NULL == provider) {
return NULL;
}
- AutoCGDataProviderRelease ar(provider);
return create_from_dataProvider(provider);
}
// Web fonts added to the the CTFont registry do not return their character set.
// Iterate through the font in this case. The existing caller caches the result,
// so the performance impact isn't too bad.
-static void populate_glyph_to_unicode_slow(CTFontRef ctFont,
- unsigned glyphCount, SkTDArray<SkUnichar>* glyphToUnicode) {
+static void populate_glyph_to_unicode_slow(CTFontRef ctFont, CFIndex glyphCount,
+ SkTDArray<SkUnichar>* glyphToUnicode) {
glyphToUnicode->setCount(glyphCount);
SkUnichar* out = glyphToUnicode->begin();
sk_bzero(out, glyphCount * sizeof(SkUnichar));
@@ -1477,23 +1396,21 @@ static void populate_glyph_to_unicode_slow(CTFontRef ctFont,
// Construct Glyph to Unicode table.
// Unicode code points that require conjugate pairs in utf16 are not
// supported.
-static void populate_glyph_to_unicode(CTFontRef ctFont,
- const unsigned glyphCount, SkTDArray<SkUnichar>* glyphToUnicode) {
- CFCharacterSetRef charSet = CTFontCopyCharacterSet(ctFont);
- AutoCFRelease autoSetRelease(charSet);
+static void populate_glyph_to_unicode(CTFontRef ctFont, CFIndex glyphCount,
+ SkTDArray<SkUnichar>* glyphToUnicode) {
+ AutoCFRelease<CFCharacterSetRef> charSet(CTFontCopyCharacterSet(ctFont));
if (!charSet) {
populate_glyph_to_unicode_slow(ctFont, glyphCount, glyphToUnicode);
return;
}
- CFDataRef bitmap = CFCharacterSetCreateBitmapRepresentation(
- kCFAllocatorDefault, charSet);
+ AutoCFRelease<CFDataRef> bitmap(CFCharacterSetCreateBitmapRepresentation(kCFAllocatorDefault,
+ charSet));
if (!bitmap) {
return;
}
CFIndex length = CFDataGetLength(bitmap);
if (!length) {
- CFSafeRelease(bitmap);
return;
}
if (length > 8192) {
@@ -1515,21 +1432,18 @@ static void populate_glyph_to_unicode(CTFontRef ctFont,
for (int j = 0; j < 8; j++) {
CGGlyph glyph;
UniChar unichar = static_cast<UniChar>((i << 3) + j);
- if (mask & (1 << j) && CTFontGetGlyphsForCharacters(ctFont,
- &unichar, &glyph, 1)) {
+ if (mask & (1 << j) && CTFontGetGlyphsForCharacters(ctFont, &unichar, &glyph, 1)) {
out[glyph] = unichar;
}
}
}
- CFSafeRelease(bitmap);
}
static bool getWidthAdvance(CTFontRef ctFont, int gId, int16_t* data) {
CGSize advance;
advance.width = 0;
CGGlyph glyph = gId;
- CTFontGetAdvancesForGlyphs(ctFont, kCTFontHorizontalOrientation, &glyph,
- &advance, 1);
+ CTFontGetAdvancesForGlyphs(ctFont, kCTFontHorizontalOrientation, &glyph, &advance, 1);
*data = sk_float_round2int(advance.width);
return true;
}
@@ -1538,11 +1452,10 @@ static bool getWidthAdvance(CTFontRef ctFont, int gId, int16_t* data) {
static void CFStringToSkString(CFStringRef src, SkString* dst) {
// Reserve enough room for the worst-case string,
// plus 1 byte for the trailing null.
- int length = CFStringGetMaximumSizeForEncoding(CFStringGetLength(src),
- kCFStringEncodingUTF8) + 1;
+ CFIndex length = CFStringGetMaximumSizeForEncoding(CFStringGetLength(src),
+ kCFStringEncodingUTF8) + 1;
dst->resize(length);
- CFStringGetCString(src, dst->writable_str(), length,
- kCFStringEncodingUTF8);
+ CFStringGetCString(src, dst->writable_str(), length, kCFStringEncodingUTF8);
// Resize to the actual UTF-8 length used, stripping the null character.
dst->resize(strlen(dst->c_str()));
}
@@ -1553,15 +1466,14 @@ SkAdvancedTypefaceMetrics* SkFontHost::GetAdvancedTypefaceMetrics(
SkAdvancedTypefaceMetrics::PerGlyphInfo perGlyphInfo,
const uint32_t* glyphIDs,
uint32_t glyphIDsCount) {
- CTFontRef ctFont = GetFontRefFromFontID(fontID);
- ctFont = CTFontCreateCopyWithAttributes(ctFont, CTFontGetUnitsPerEm(ctFont),
- NULL, NULL);
+ CTFontRef originalCTFont = GetFontRefFromFontID(fontID);
+ AutoCFRelease<CTFontRef> ctFont(CTFontCreateCopyWithAttributes(
+ originalCTFont, CTFontGetUnitsPerEm(originalCTFont), NULL, NULL));
SkAdvancedTypefaceMetrics* info = new SkAdvancedTypefaceMetrics;
{
- CFStringRef fontName = CTFontCopyPostScriptName(ctFont);
+ AutoCFRelease<CFStringRef> fontName(CTFontCopyPostScriptName(ctFont));
CFStringToSkString(fontName, &info->fFontName);
- CFRelease(fontName);
}
info->fMultiMaster = false;
@@ -1587,7 +1499,6 @@ SkAdvancedTypefaceMetrics* SkFontHost::GetAdvancedTypefaceMetrics(
info->fStemV = 0;
info->fCapHeight = 0;
info->fBBox = SkIRect::MakeEmpty();
- CFSafeRelease(ctFont);
return info;
}
@@ -1599,13 +1510,11 @@ SkAdvancedTypefaceMetrics* SkFontHost::GetAdvancedTypefaceMetrics(
if (symbolicTraits & kCTFontItalicTrait) {
info->fStyle |= SkAdvancedTypefaceMetrics::kItalic_Style;
}
- CTFontStylisticClass stylisticClass = symbolicTraits &
- kCTFontClassMaskTrait;
+ CTFontStylisticClass stylisticClass = symbolicTraits & kCTFontClassMaskTrait;
if (stylisticClass & kCTFontSymbolicClass) {
info->fStyle |= SkAdvancedTypefaceMetrics::kSymbolic_Style;
}
- if (stylisticClass >= kCTFontOldStyleSerifsClass
- && stylisticClass <= kCTFontSlabSerifsClass) {
+ if (stylisticClass >= kCTFontOldStyleSerifsClass && stylisticClass <= kCTFontSlabSerifsClass) {
info->fStyle |= SkAdvancedTypefaceMetrics::kSerif_Style;
} else if (stylisticClass & kCTFontScriptsClass) {
info->fStyle |= SkAdvancedTypefaceMetrics::kScript_Style;
@@ -1631,7 +1540,7 @@ SkAdvancedTypefaceMetrics* SkFontHost::GetAdvancedTypefaceMetrics(
CGRect boundingRects[count];
if (CTFontGetGlyphsForCharacters(ctFont, stem_chars, glyphs, count)) {
CTFontGetBoundingRectsForGlyphs(ctFont, kCTFontHorizontalOrientation,
- glyphs, boundingRects, count);
+ glyphs, boundingRects, count);
for (size_t i = 0; i < count; i++) {
int16_t width = (int16_t) boundingRects[i].size.width;
if (width > 0 && width < min_width) {
@@ -1644,8 +1553,7 @@ SkAdvancedTypefaceMetrics* SkFontHost::GetAdvancedTypefaceMetrics(
if (false) { // TODO: haven't figured out how to know if font is embeddable
// (information is in the OS/2 table)
info->fType = SkAdvancedTypefaceMetrics::kNotEmbeddable_Font;
- } else if (perGlyphInfo &
- SkAdvancedTypefaceMetrics::kHAdvance_PerGlyphInfo) {
+ } else if (perGlyphInfo & SkAdvancedTypefaceMetrics::kHAdvance_PerGlyphInfo) {
if (info->fStyle & SkAdvancedTypefaceMetrics::kFixedPitch_Style) {
skia_advanced_typeface_metrics_utils::appendRange(&info->fGlyphWidths, 0);
info->fGlyphWidths->fAdvance.append(1, &min_width);
@@ -1653,57 +1561,68 @@ SkAdvancedTypefaceMetrics* SkFontHost::GetAdvancedTypefaceMetrics(
SkAdvancedTypefaceMetrics::WidthRange::kDefault);
} else {
info->fGlyphWidths.reset(
- skia_advanced_typeface_metrics_utils::getAdvanceData(ctFont,
+ skia_advanced_typeface_metrics_utils::getAdvanceData(ctFont.get(),
glyphCount,
glyphIDs,
glyphIDsCount,
&getWidthAdvance));
}
}
-
- CFSafeRelease(ctFont);
return info;
}
///////////////////////////////////////////////////////////////////////////////
-struct FontHeader {
- SkFixed fVersion;
- uint16_t fNumTables;
- uint16_t fSearchRange;
- uint16_t fEntrySelector;
- uint16_t fRangeShift;
-};
-
-struct TableEntry {
- uint32_t fTag;
- uint32_t fCheckSum;
- uint32_t fOffset;
- uint32_t fLength;
-};
+static SK_SFNT_ULONG get_font_type_tag(SkFontID uniqueID) {
+ CTFontRef ctFont = GetFontRefFromFontID(uniqueID);
+ AutoCFRelease<CFNumberRef> fontFormatRef(
+ static_cast<CFNumberRef>(CTFontCopyAttribute(ctFont, kCTFontFormatAttribute)));
+ if (!fontFormatRef) {
+ return 0;
+ }
-static uint32_t CalcTableCheckSum(uint32_t *table, uint32_t numberOfBytesInTable) {
- uint32_t sum = 0;
- uint32_t nLongs = (numberOfBytesInTable + 3) / 4;
+ SInt32 fontFormatValue;
+ if (!CFNumberGetValue(fontFormatRef, kCFNumberSInt32Type, &fontFormatValue)) {
+ return 0;
+ }
- while (nLongs-- > 0) {
- sum += SkEndian_SwapBE32(*table++);
+ switch (fontFormatValue) {
+ case kCTFontFormatOpenTypePostScript:
+ return SkSFNTHeader::fontType_OpenTypeCFF::TAG;
+ case kCTFontFormatOpenTypeTrueType:
+ return SkSFNTHeader::fontType_WindowsTrueType::TAG;
+ case kCTFontFormatTrueType:
+ return SkSFNTHeader::fontType_MacTrueType::TAG;
+ case kCTFontFormatPostScript:
+ return SkSFNTHeader::fontType_PostScript::TAG;
+ case kCTFontFormatBitmap:
+ return SkSFNTHeader::fontType_MacTrueType::TAG;
+ case kCTFontFormatUnrecognized:
+ default:
+ //CT seems to be unreliable in being able to obtain the type,
+ //even if all we want is the first four bytes of the font resource.
+ //Just the presence of the FontForge 'FFTM' table seems to throw it off.
+ return SkSFNTHeader::fontType_WindowsTrueType::TAG;
}
- return sum;
}
SkStream* SkFontHost::OpenStream(SkFontID uniqueID) {
+ SK_SFNT_ULONG fontType = get_font_type_tag(uniqueID);
+ if (0 == fontType) {
+ return NULL;
+ }
+
// get table tags
- int tableCount = CountTables(uniqueID);
+ int numTables = CountTables(uniqueID);
SkTDArray<SkFontTableTag> tableTags;
- tableTags.setCount(tableCount);
+ tableTags.setCount(numTables);
GetTableTags(uniqueID, tableTags.begin());
// calc total size for font, save sizes
SkTDArray<size_t> tableSizes;
- size_t totalSize = sizeof(FontHeader) + sizeof(TableEntry) * tableCount;
- for (int index = 0; index < tableCount; ++index) {
- size_t tableSize = GetTableSize(uniqueID, tableTags[index]);
+ size_t totalSize = sizeof(SkSFNTHeader) + sizeof(SkSFNTHeader::TableDirectoryEntry) * numTables;
+ for (int tableIndex = 0; tableIndex < numTables; ++tableIndex) {
+ size_t tableSize = GetTableSize(uniqueID, tableTags[tableIndex]);
totalSize += (tableSize + 3) & ~3;
*tableSizes.append() = tableSize;
}
@@ -1717,33 +1636,34 @@ SkStream* SkFontHost::OpenStream(SkFontID uniqueID) {
// compute font header entries
uint16_t entrySelector = 0;
uint16_t searchRange = 1;
- while (searchRange < tableCount >> 1) {
+ while (searchRange < numTables >> 1) {
entrySelector++;
searchRange <<= 1;
}
searchRange <<= 4;
- uint16_t rangeShift = (tableCount << 4) - searchRange;
+ uint16_t rangeShift = (numTables << 4) - searchRange;
- // write font header (also called sfnt header, offset subtable)
- FontHeader* offsetTable = (FontHeader*)dataPtr;
- offsetTable->fVersion = SkEndian_SwapBE32(SK_Fixed1);
- offsetTable->fNumTables = SkEndian_SwapBE16(tableCount);
- offsetTable->fSearchRange = SkEndian_SwapBE16(searchRange);
- offsetTable->fEntrySelector = SkEndian_SwapBE16(entrySelector);
- offsetTable->fRangeShift = SkEndian_SwapBE16(rangeShift);
- dataPtr += sizeof(FontHeader);
+ // write font header
+ SkSFNTHeader* header = (SkSFNTHeader*)dataPtr;
+ header->fontType = fontType;
+ header->numTables = SkEndian_SwapBE16(numTables);
+ header->searchRange = SkEndian_SwapBE16(searchRange);
+ header->entrySelector = SkEndian_SwapBE16(entrySelector);
+ header->rangeShift = SkEndian_SwapBE16(rangeShift);
+ dataPtr += sizeof(SkSFNTHeader);
// write tables
- TableEntry* entry = (TableEntry*)dataPtr;
- dataPtr += sizeof(TableEntry) * tableCount;
- for (int index = 0; index < tableCount; ++index) {
- size_t tableSize = tableSizes[index];
- GetTableData(uniqueID, tableTags[index], 0, tableSize, dataPtr);
- entry->fTag = SkEndian_SwapBE32(tableTags[index]);
- entry->fCheckSum = SkEndian_SwapBE32(CalcTableCheckSum(
- (uint32_t*)dataPtr, tableSize));
- entry->fOffset = SkEndian_SwapBE32(dataPtr - dataStart);
- entry->fLength = SkEndian_SwapBE32(tableSize);
+ SkSFNTHeader::TableDirectoryEntry* entry = (SkSFNTHeader::TableDirectoryEntry*)dataPtr;
+ dataPtr += sizeof(SkSFNTHeader::TableDirectoryEntry) * numTables;
+ for (int tableIndex = 0; tableIndex < numTables; ++tableIndex) {
+ size_t tableSize = tableSizes[tableIndex];
+ GetTableData(uniqueID, tableTags[tableIndex], 0, tableSize, dataPtr);
+ entry->tag = SkEndian_SwapBE32(tableTags[tableIndex]);
+ entry->checksum = SkEndian_SwapBE32(SkOTUtils::CalcTableChecksum((SK_OT_ULONG*)dataPtr,
+ tableSize));
+ entry->offset = SkEndian_SwapBE32(dataPtr - dataStart);
+ entry->logicalLength = SkEndian_SwapBE32(tableSize);
+
dataPtr += (tableSize + 3) & ~3;
++entry;
}
@@ -1751,10 +1671,9 @@ SkStream* SkFontHost::OpenStream(SkFontID uniqueID) {
return stream;
}
-size_t SkFontHost::GetFileName(SkFontID fontID, char path[], size_t length,
- int32_t* index) {
+size_t SkFontHost::GetFileName(SkFontID fontID, char path[], size_t length, int32_t* index) {
SkDEBUGFAIL("SkFontHost::GetFileName unimplemented");
- return(0);
+ return 0;
}
///////////////////////////////////////////////////////////////////////////////
@@ -1769,9 +1688,9 @@ static const char* get_str(CFStringRef ref, SkString* str) {
}
void SkFontHost::Serialize(const SkTypeface* face, SkWStream* stream) {
- CTFontRef ctFont = typeface_to_fontref(face);
+ CTFontRef ctFont = typeface_to_fontref(face);
SkFontDescriptor desc(face->style());
- SkString tmpStr;
+ SkString tmpStr;
desc.setFamilyName(get_str(CTFontCopyFamilyName(ctFont), &tmpStr));
desc.setFullName(get_str(CTFontCopyFullName(ctFont), &tmpStr));
@@ -1792,8 +1711,7 @@ SkTypeface* SkFontHost::Deserialize(SkStream* stream) {
size_t size = stream->readPackedUInt();
stream->skip(size);
- return SkFontHost::CreateTypeface(NULL, desc.getFamilyName(),
- desc.getStyle());
+ return SkFontHost::CreateTypeface(NULL, desc.getFamilyName(), desc.getStyle());
}
///////////////////////////////////////////////////////////////////////////////
@@ -1817,16 +1735,19 @@ void SkFontHost::FilterRec(SkScalerContext::Rec* rec, SkTypeface*) {
rec->fFlags &= ~flagsWeDontSupport;
+ bool lcdSupport = supports_LCD();
+
// Only two levels of hinting are supported.
// kNo_Hinting means avoid CoreGraphics outline dilation.
// kNormal_Hinting means CoreGraphics outline dilation is allowed.
- SkPaint::Hinting h = rec->getHinting();
- if (SkPaint::kSlight_Hinting == h) {
- h = SkPaint::kNo_Hinting;
- } else if (SkPaint::kFull_Hinting == h) {
- h = SkPaint::kNormal_Hinting;
+ // If there is no lcd support, hinting (dilation) cannot be supported.
+ SkPaint::Hinting hinting = rec->getHinting();
+ if (SkPaint::kSlight_Hinting == hinting || !lcdSupport) {
+ hinting = SkPaint::kNo_Hinting;
+ } else if (SkPaint::kFull_Hinting == hinting) {
+ hinting = SkPaint::kNormal_Hinting;
}
- rec->setHinting(h);
+ rec->setHinting(hinting);
// FIXME: lcd smoothed un-hinted rasterization unsupported.
// Tracked by http://code.google.com/p/skia/issues/detail?id=915 .
@@ -1847,7 +1768,6 @@ void SkFontHost::FilterRec(SkScalerContext::Rec* rec, SkTypeface*) {
// Currenly side with LCD, effectively ignoring the hinting setting.
// [LCD][yes-hint]: generate LCD using CoreGraphic's LCD output.
- bool lcdSupport = supports_LCD();
if (isLCDFormat(rec->fMaskFormat)) {
if (lcdSupport) {
//CoreGraphics creates 555 masks for smoothed text anyway.
@@ -1858,13 +1778,15 @@ void SkFontHost::FilterRec(SkScalerContext::Rec* rec, SkTypeface*) {
}
}
- if (lcdSupport) {
- //CoreGraphics dialates smoothed text as needed.
- rec->setContrast(0);
- } else {
+ // Unhinted A8 masks (those not derived from LCD masks) must respect SK_GAMMA_APPLY_TO_A8.
+ // All other masks can use regular gamma.
+ if (SkMask::kA8_Format == rec->fMaskFormat && SkPaint::kNo_Hinting == hinting) {
#ifndef SK_GAMMA_APPLY_TO_A8
rec->ignorePreBlend();
#endif
+ } else {
+ //CoreGraphics dialates smoothed text as needed.
+ rec->setContrast(0);
}
}
@@ -1872,31 +1794,26 @@ void SkFontHost::FilterRec(SkScalerContext::Rec* rec, SkTypeface*) {
int SkFontHost::CountTables(SkFontID fontID) {
CTFontRef ctFont = GetFontRefFromFontID(fontID);
- CFArrayRef cfArray = CTFontCopyAvailableTables(ctFont,
- kCTFontTableOptionNoOptions);
+ AutoCFRelease<CFArrayRef> cfArray(CTFontCopyAvailableTables(ctFont,
+ kCTFontTableOptionNoOptions));
if (NULL == cfArray) {
return 0;
}
-
- AutoCFRelease ar(cfArray);
return CFArrayGetCount(cfArray);
}
int SkFontHost::GetTableTags(SkFontID fontID, SkFontTableTag tags[]) {
CTFontRef ctFont = GetFontRefFromFontID(fontID);
- CFArrayRef cfArray = CTFontCopyAvailableTables(ctFont,
- kCTFontTableOptionNoOptions);
+ AutoCFRelease<CFArrayRef> cfArray(CTFontCopyAvailableTables(ctFont,
+ kCTFontTableOptionNoOptions));
if (NULL == cfArray) {
return 0;
}
- AutoCFRelease ar(cfArray);
-
int count = CFArrayGetCount(cfArray);
if (tags) {
for (int i = 0; i < count; ++i) {
- uintptr_t fontTag = reinterpret_cast<uintptr_t>(
- CFArrayGetValueAtIndex(cfArray, i));
+ uintptr_t fontTag = reinterpret_cast<uintptr_t>(CFArrayGetValueAtIndex(cfArray, i));
tags[i] = static_cast<SkFontTableTag>(fontTag);
}
}
@@ -1907,37 +1824,31 @@ int SkFontHost::GetTableTags(SkFontID fontID, SkFontTableTag tags[]) {
// the CGFont data may work. While the CGFont may always provide the
// right result, leave the CTFont code path to minimize disruption.
static CFDataRef copyTableFromFont(CTFontRef ctFont, SkFontTableTag tag) {
- CFDataRef data = CTFontCopyTable(ctFont, (CTFontTableTag) tag,
- kCTFontTableOptionNoOptions);
+ CFDataRef data = CTFontCopyTable(ctFont, (CTFontTableTag) tag, kCTFontTableOptionNoOptions);
if (NULL == data) {
- CGFontRef cgFont = CTFontCopyGraphicsFont(ctFont, NULL);
+ AutoCFRelease<CGFontRef> cgFont(CTFontCopyGraphicsFont(ctFont, NULL));
data = CGFontCopyTableForTag(cgFont, tag);
- CGFontRelease(cgFont);
}
return data;
}
size_t SkFontHost::GetTableSize(SkFontID fontID, SkFontTableTag tag) {
CTFontRef ctFont = GetFontRefFromFontID(fontID);
- CFDataRef srcData = copyTableFromFont(ctFont, tag);
+ AutoCFRelease<CFDataRef> srcData(copyTableFromFont(ctFont, tag));
if (NULL == srcData) {
return 0;
}
-
- AutoCFRelease ar(srcData);
return CFDataGetLength(srcData);
}
size_t SkFontHost::GetTableData(SkFontID fontID, SkFontTableTag tag,
size_t offset, size_t length, void* dst) {
CTFontRef ctFont = GetFontRefFromFontID(fontID);
- CFDataRef srcData = copyTableFromFont(ctFont, tag);
+ AutoCFRelease<CFDataRef> srcData(copyTableFromFont(ctFont, tag));
if (NULL == srcData) {
return 0;
}
- AutoCFRelease ar(srcData);
-
size_t srcSize = CFDataGetLength(srcData);
if (offset >= srcSize) {
return 0;
diff --git a/src/ports/SkFontHost_win.cpp b/src/ports/SkFontHost_win.cpp
index f2603e99a8..49289712e1 100755
--- a/src/ports/SkFontHost_win.cpp
+++ b/src/ports/SkFontHost_win.cpp
@@ -548,9 +548,10 @@ protected:
virtual uint16_t generateCharToGlyph(SkUnichar uni) SK_OVERRIDE;
virtual void generateAdvance(SkGlyph* glyph) SK_OVERRIDE;
virtual void generateMetrics(SkGlyph* glyph) SK_OVERRIDE;
- virtual void generateImage(const SkGlyph& glyph, SkMaskGamma::PreBlend* maskPreBlend) SK_OVERRIDE;
+ virtual void generateImage(const SkGlyph& glyph) SK_OVERRIDE;
virtual void generatePath(const SkGlyph& glyph, SkPath* path) SK_OVERRIDE;
- virtual void generateFontMetrics(SkPaint::FontMetrics* mX, SkPaint::FontMetrics* mY) SK_OVERRIDE;
+ virtual void generateFontMetrics(SkPaint::FontMetrics* mX,
+ SkPaint::FontMetrics* mY) SK_OVERRIDE;
private:
HDCOffscreen fOffscreen;
@@ -1134,20 +1135,10 @@ static inline unsigned clamp255(unsigned x) {
return x - (x >> 8);
}
-void SkScalerContext_Windows::generateImage(const SkGlyph& glyph, SkMaskGamma::PreBlend* maskPreBlend) {
+void SkScalerContext_Windows::generateImage(const SkGlyph& glyph) {
SkAutoMutexAcquire ac(gFTMutex);
SkASSERT(fDDC);
- //Must be careful not to use these if maskPreBlend == NULL
- const uint8_t* tableR = NULL;
- const uint8_t* tableG = NULL;
- const uint8_t* tableB = NULL;
- if (maskPreBlend) {
- tableR = maskPreBlend->fR;
- tableG = maskPreBlend->fG;
- tableB = maskPreBlend->fB;
- }
-
const bool isBW = SkMask::kBW_Format == fRec.fMaskFormat;
const bool isAA = !isLCD(fRec);
@@ -1202,10 +1193,10 @@ void SkScalerContext_Windows::generateImage(const SkGlyph& glyph, SkMaskGamma::P
// since the caller may require A8 for maskfilters, we can't check for BW
// ... until we have the caller tell us that explicitly
const SkGdiRGB* src = (const SkGdiRGB*)bits;
- if (maskPreBlend) {
- rgb_to_a8<true>(src, srcRB, glyph, tableG);
+ if (fPreBlend.isApplicable()) {
+ rgb_to_a8<true>(src, srcRB, glyph, fPreBlend.fG);
} else {
- rgb_to_a8<false>(src, srcRB, glyph, tableG);
+ rgb_to_a8<false>(src, srcRB, glyph, fPreBlend.fG);
}
} else { // LCD16
const SkGdiRGB* src = (const SkGdiRGB*)bits;
@@ -1214,17 +1205,21 @@ void SkScalerContext_Windows::generateImage(const SkGlyph& glyph, SkMaskGamma::P
((SkGlyph*)&glyph)->fMaskFormat = SkMask::kBW_Format;
} else {
if (SkMask::kLCD16_Format == glyph.fMaskFormat) {
- if (maskPreBlend) {
- rgb_to_lcd16<true>(src, srcRB, glyph, tableR, tableG, tableB);
+ if (fPreBlend.isApplicable()) {
+ rgb_to_lcd16<true>(src, srcRB, glyph,
+ fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
} else {
- rgb_to_lcd16<false>(src, srcRB, glyph, tableR, tableG, tableB);
+ rgb_to_lcd16<false>(src, srcRB, glyph,
+ fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
}
} else {
SkASSERT(SkMask::kLCD32_Format == glyph.fMaskFormat);
- if (maskPreBlend) {
- rgb_to_lcd32<true>(src, srcRB, glyph, tableR, tableG, tableB);
+ if (fPreBlend.isApplicable()) {
+ rgb_to_lcd32<true>(src, srcRB, glyph,
+ fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
} else {
- rgb_to_lcd32<false>(src, srcRB, glyph, tableR, tableG, tableB);
+ rgb_to_lcd32<false>(src, srcRB, glyph,
+ fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
}
}
}
@@ -1323,19 +1318,13 @@ static void logfont_for_name(const char* familyName, LOGFONT& lf) {
#endif
}
-static void logfont_to_name(const LOGFONT& lf, SkString* s) {
+static void tchar_to_skstring(const TCHAR* t, SkString* s) {
#ifdef UNICODE
- // Get the buffer size needed first.
- size_t str_len = WideCharToMultiByte(CP_UTF8, 0, lf.lfFaceName, -1, NULL,
- 0, NULL, NULL);
- // Allocate a buffer (str_len already has terminating null accounted for).
- s->resize(str_len);
- // Now actually convert the string.
- WideCharToMultiByte(CP_UTF8, 0, lf.lfFaceName, -1,
- s->writable_str(), str_len,
- NULL, NULL);
+ size_t sSize = WideCharToMultiByte(CP_UTF8, 0, t, -1, NULL, 0, NULL, NULL);
+ s->resize(sSize);
+ WideCharToMultiByte(CP_UTF8, 0, t, -1, s->writable_str(), sSize, NULL, NULL);
#else
- s->set(lf.lfFaceName);
+ s->set(t);
#endif
}
@@ -1343,8 +1332,38 @@ void SkFontHost::Serialize(const SkTypeface* rawFace, SkWStream* stream) {
const LogFontTypeface* face = static_cast<const LogFontTypeface*>(rawFace);
SkFontDescriptor descriptor(face->style());
+ // Get the actual name of the typeface. The logfont may not know this.
+ HFONT font = CreateFontIndirect(&face->fLogFont);
+
+ HDC deviceContext = ::CreateCompatibleDC(NULL);
+ HFONT savefont = (HFONT)SelectObject(deviceContext, font);
+
+ int fontNameLen; //length of fontName in TCHARS.
+ if (0 == (fontNameLen = GetTextFace(deviceContext, 0, NULL))) {
+ SkFontHost::EnsureTypefaceAccessible(*rawFace);
+ if (0 == (fontNameLen = GetTextFace(deviceContext, 0, NULL))) {
+ fontNameLen = 0;
+ }
+ }
+
+ SkAutoSTArray<LF_FULLFACESIZE, TCHAR> fontName(fontNameLen+1);
+ if (0 == GetTextFace(deviceContext, fontNameLen, fontName.get())) {
+ SkFontHost::EnsureTypefaceAccessible(*rawFace);
+ if (0 == GetTextFace(deviceContext, fontNameLen, fontName.get())) {
+ fontName[0] = 0;
+ }
+ }
+
+ if (deviceContext) {
+ ::SelectObject(deviceContext, savefont);
+ ::DeleteDC(deviceContext);
+ }
+ if (font) {
+ ::DeleteObject(font);
+ }
+
SkString familyName;
- logfont_to_name(face->fLogFont, &familyName);
+ tchar_to_skstring(fontName.get(), &familyName);
descriptor.setFamilyName(familyName.c_str());
//TODO: FileName and PostScriptName currently unsupported.
@@ -1434,7 +1453,7 @@ SkAdvancedTypefaceMetrics* SkFontHost::GetAdvancedTypefaceMetrics(
info->fMultiMaster = false;
info->fLastGlyphID = SkToU16(glyphCount - 1);
info->fStyle = 0;
- logfont_to_name(lf, &info->fFontName);
+ tchar_to_skstring(lf.lfFaceName, &info->fFontName);
if (perGlyphInfo & SkAdvancedTypefaceMetrics::kToUnicode_PerGlyphInfo) {
populate_glyph_to_unicode(hdc, glyphCount, &(info->fGlyphToUnicode));
diff --git a/src/ports/SkFontHost_win_dw.cpp b/src/ports/SkFontHost_win_dw.cpp
index 82d44eeb33..67ee5c4927 100644
--- a/src/ports/SkFontHost_win_dw.cpp
+++ b/src/ports/SkFontHost_win_dw.cpp
@@ -489,8 +489,7 @@ protected:
virtual uint16_t generateCharToGlyph(SkUnichar uni) SK_OVERRIDE;
virtual void generateAdvance(SkGlyph* glyph) SK_OVERRIDE;
virtual void generateMetrics(SkGlyph* glyph) SK_OVERRIDE;
- virtual void generateImage(const SkGlyph& glyph,
- SkMaskGamma::PreBlend* maskPreBlend) SK_OVERRIDE;
+ virtual void generateImage(const SkGlyph& glyph) SK_OVERRIDE;
virtual void generatePath(const SkGlyph& glyph, SkPath* path) SK_OVERRIDE;
virtual void generateFontMetrics(SkPaint::FontMetrics* mX,
SkPaint::FontMetrics* mY) SK_OVERRIDE;
@@ -975,20 +974,9 @@ static void rgb_to_lcd32(const uint8_t* SK_RESTRICT src, const SkGlyph& glyph,
}
}
-void SkScalerContext_Windows::generateImage(const SkGlyph& glyph,
- SkMaskGamma::PreBlend* maskPreBlend) {
+void SkScalerContext_Windows::generateImage(const SkGlyph& glyph) {
SkAutoMutexAcquire ac(gFTMutex);
- //Must be careful not to use these if maskPreBlend == NULL
- const uint8_t* tableR = NULL;
- const uint8_t* tableG = NULL;
- const uint8_t* tableB = NULL;
- if (maskPreBlend) {
- tableR = maskPreBlend->fR;
- tableG = maskPreBlend->fG;
- tableB = maskPreBlend->fB;
- }
-
const bool isBW = SkMask::kBW_Format == fRec.fMaskFormat;
const bool isAA = !isLCD(fRec);
@@ -1006,23 +994,23 @@ void SkScalerContext_Windows::generateImage(const SkGlyph& glyph,
if (isBW) {
bilevel_to_bw(src, glyph);
} else if (isAA) {
- if (maskPreBlend) {
- rgb_to_a8<true>(src, glyph, tableG);
+ if (fPreBlend.isApplicable()) {
+ rgb_to_a8<true>(src, glyph, fPreBlend.fG);
} else {
- rgb_to_a8<false>(src, glyph, tableG);
+ rgb_to_a8<false>(src, glyph, fPreBlend.fG);
}
} else if (SkMask::kLCD16_Format == glyph.fMaskFormat) {
- if (maskPreBlend) {
- rgb_to_lcd16<true>(src, glyph, tableR, tableG, tableB);
+ if (fPreBlend.isApplicable()) {
+ rgb_to_lcd16<true>(src, glyph, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
} else {
- rgb_to_lcd16<false>(src, glyph, tableR, tableG, tableB);
+ rgb_to_lcd16<false>(src, glyph, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
}
} else {
SkASSERT(SkMask::kLCD32_Format == glyph.fMaskFormat);
- if (maskPreBlend) {
- rgb_to_lcd32<true>(src, glyph, tableR, tableG, tableB);
+ if (fPreBlend.isApplicable()) {
+ rgb_to_lcd32<true>(src, glyph, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
} else {
- rgb_to_lcd32<false>(src, glyph, tableR, tableG, tableB);
+ rgb_to_lcd32<false>(src, glyph, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
}
}
}
diff --git a/src/ports/SkGlobalInitialization_default.cpp b/src/ports/SkGlobalInitialization_default.cpp
index a764dd178c..4ed7f5e16d 100644
--- a/src/ports/SkGlobalInitialization_default.cpp
+++ b/src/ports/SkGlobalInitialization_default.cpp
@@ -41,7 +41,9 @@
#include "SkLightingImageFilter.h"
#include "SkMagnifierImageFilter.h"
#include "SkMatrixConvolutionImageFilter.h"
+#include "SkMergeImageFilter.h"
#include "SkMorphologyImageFilter.h"
+#include "SkOffsetImageFilter.h"
#include "SkPixelXorXfermode.h"
#include "SkStippleMaskFilter.h"
#include "SkTableColorFilter.h"
diff --git a/src/ports/SkImageDecoder_CG.cpp b/src/ports/SkImageDecoder_CG.cpp
index bcd3e3741c..afe4cf44ac 100644
--- a/src/ports/SkImageDecoder_CG.cpp
+++ b/src/ports/SkImageDecoder_CG.cpp
@@ -6,6 +6,7 @@
* found in the LICENSE file.
*/
+#include "SkColorPriv.h"
#include "SkImageDecoder.h"
#include "SkImageEncoder.h"
@@ -77,15 +78,32 @@ bool SkImageDecoder_CG::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
}
bm->lockPixels();
- bm->eraseColor(0);
+ bm->eraseColor(SK_ColorTRANSPARENT);
// use the same colorspace, so we don't change the pixels at all
CGColorSpaceRef cs = CGImageGetColorSpace(image);
- CGContextRef cg = CGBitmapContextCreate(bm->getPixels(), width, height,
- 8, bm->rowBytes(), cs, BITMAP_INFO);
+ CGContextRef cg = CGBitmapContextCreate(bm->getPixels(), width, height, 8, bm->rowBytes(), cs, BITMAP_INFO);
+ if (NULL == cg) {
+ // perhaps the image's colorspace does not work for a context, so try just rgb
+ cs = CGColorSpaceCreateDeviceRGB();
+ cg = CGBitmapContextCreate(bm->getPixels(), width, height, 8, bm->rowBytes(), cs, BITMAP_INFO);
+ CFRelease(cs);
+ }
CGContextDrawImage(cg, CGRectMake(0, 0, width, height), image);
CGContextRelease(cg);
+ CGImageAlphaInfo info = CGImageGetAlphaInfo(image);
+ switch (info) {
+ case kCGImageAlphaNone:
+ case kCGImageAlphaNoneSkipLast:
+ case kCGImageAlphaNoneSkipFirst:
+ SkASSERT(SkBitmap::ComputeIsOpaque(*bm));
+ bm->setIsOpaque(true);
+ break;
+ default:
+ // we don't know if we're opaque or not, so compute it.
+ bm->computeAndSetOpaquePredicate();
+ }
bm->unlockPixels();
return true;
}
diff --git a/src/ports/SkImageDecoder_WIC.cpp b/src/ports/SkImageDecoder_WIC.cpp
index 79d107adec..d83611fb4e 100644
--- a/src/ports/SkImageDecoder_WIC.cpp
+++ b/src/ports/SkImageDecoder_WIC.cpp
@@ -17,6 +17,7 @@
#include "SkMovie.h"
#include "SkStream.h"
#include "SkTScopedComPtr.h"
+#include "SkUnPreMultiply.h"
class SkImageDecoder_WIC : public SkImageDecoder {
protected:
@@ -126,7 +127,7 @@ bool SkImageDecoder_WIC::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
//Copy the pixels into the bitmap.
if (SUCCEEDED(hr)) {
SkAutoLockPixels alp(*bm);
- bm->eraseColor(0);
+ bm->eraseColor(SK_ColorTRANSPARENT);
const int stride = bm->rowBytes();
hr = piBitmapSourceConverted->CopyPixels(
NULL, //Get all the pixels
@@ -134,6 +135,9 @@ bool SkImageDecoder_WIC::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
stride * height,
reinterpret_cast<BYTE *>(bm->getPixels())
);
+
+ // Note: we don't need to premultiply here since we specified PBGRA
+ bm->computeAndSetOpaquePredicate();
}
return SUCCEEDED(hr);
@@ -183,7 +187,7 @@ bool SkImageEncoder_WIC::onEncode(SkWStream* stream
//Convert to 8888 if needed.
const SkBitmap* bitmap;
SkBitmap bitmapCopy;
- if (SkBitmap::kARGB_8888_Config == bitmapOrig.config()) {
+ if (SkBitmap::kARGB_8888_Config == bitmapOrig.config() && bitmapOrig.isOpaque()) {
bitmap = &bitmapOrig;
} else {
if (!bitmapOrig.copyTo(&bitmapCopy, SkBitmap::kARGB_8888_Config)) {
@@ -192,6 +196,23 @@ bool SkImageEncoder_WIC::onEncode(SkWStream* stream
bitmap = &bitmapCopy;
}
+ // We cannot use PBGRA so we need to unpremultiply ourselves
+ if (!bitmap->isOpaque()) {
+ SkAutoLockPixels alp(*bitmap);
+
+ uint8_t* pixels = reinterpret_cast<uint8_t*>(bitmap->getPixels());
+ for (int y = 0; y < bitmap->height(); ++y) {
+ for (int x = 0; x < bitmap->width(); ++x) {
+ uint8_t* bytes = pixels + y * bitmap->rowBytes() + x * bitmap->bytesPerPixel();
+
+ SkPMColor* src = reinterpret_cast<SkPMColor*>(bytes);
+ SkColor* dst = reinterpret_cast<SkColor*>(bytes);
+
+ *dst = SkUnPreMultiply::PMColorToColor(*src);
+ }
+ }
+ }
+
//Initialize COM.
SkAutoCoInitialize scopedCo;
if (!scopedCo.succeeded()) {
diff --git a/src/ports/SkImageRef_ashmem.cpp b/src/ports/SkImageRef_ashmem.cpp
index 542630930a..f8a9bb9f90 100644
--- a/src/ports/SkImageRef_ashmem.cpp
+++ b/src/ports/SkImageRef_ashmem.cpp
@@ -10,9 +10,10 @@
#include "SkFlattenableBuffers.h"
#include "SkThread.h"
+#include "android/ashmem.h"
+
#include <sys/mman.h>
#include <unistd.h>
-#include <cutils/ashmem.h>
//#define TRACE_ASH_PURGE // just trace purges
diff --git a/src/ports/SkOSFile_stdio.cpp b/src/ports/SkOSFile_stdio.cpp
index 1254394431..9100f6d122 100644
--- a/src/ports/SkOSFile_stdio.cpp
+++ b/src/ports/SkOSFile_stdio.cpp
@@ -9,8 +9,17 @@
#include "SkOSFile.h"
-#include <stdio.h>
#include <errno.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#ifdef _WIN32
+#include <direct.h>
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
SkFILE* sk_fopen(const char path[], SkFILE_Flags flags)
{
@@ -98,3 +107,46 @@ void sk_fclose(SkFILE* f)
::fclose((FILE*)f);
}
+bool sk_exists(const char *path)
+{
+#ifdef _WIN32
+ return (0 == _access(path, 0));
+#else
+ return (0 == access(path, 0));
+#endif
+}
+
+bool sk_isdir(const char *path)
+{
+ struct stat status;
+ if (0 != stat(path, &status)) {
+ return false;
+ }
+ return SkToBool(status.st_mode & S_IFDIR);
+}
+
+bool sk_mkdir(const char* path)
+{
+ if (sk_isdir(path)) {
+ return true;
+ }
+ if (sk_exists(path)) {
+ fprintf(stderr,
+ "sk_mkdir: path '%s' already exists but is not a directory\n",
+ path);
+ return false;
+ }
+
+ int retval;
+#ifdef _WIN32
+ retval = _mkdir(path);
+#else
+ retval = mkdir(path, 0777);
+#endif
+ if (0 == retval) {
+ return true;
+ } else {
+ fprintf(stderr, "sk_mkdir: error %d creating dir '%s'\n", errno, path);
+ return false;
+ }
+}
diff --git a/src/ports/SkThread_win.cpp b/src/ports/SkThread_win.cpp
index 91a5ceb7ec..bc038b2289 100644
--- a/src/ports/SkThread_win.cpp
+++ b/src/ports/SkThread_win.cpp
@@ -93,3 +93,44 @@ void SkTLS::PlatformSetSpecific(void* ptr) {
(void)TlsSetValue(gTlsIndex, ptr);
}
+// Call TLS destructors on thread exit. Code based on Chromium's
+// base/threading/thread_local_storage_win.cc
+#ifdef _WIN64
+
+#pragma comment(linker, "/INCLUDE:_tls_used")
+#pragma comment(linker, "/INCLUDE:skia_tls_callback")
+
+#else
+
+#pragma comment(linker, "/INCLUDE:__tls_used")
+#pragma comment(linker, "/INCLUDE:_skia_tls_callback")
+
+#endif
+
+void NTAPI onTLSCallback(PVOID unused, DWORD reason, PVOID unused2) {
+ if ((DLL_THREAD_DETACH == reason || DLL_PROCESS_DETACH == reason) && gOnce) {
+ void* ptr = TlsGetValue(gTlsIndex);
+ if (ptr != NULL) {
+ SkTLS::Destructor(ptr);
+ TlsSetValue(gTlsIndex, NULL);
+ }
+ }
+}
+
+extern "C" {
+
+#ifdef _WIN64
+
+#pragma const_seg(".CRT$XLB")
+extern const PIMAGE_TLS_CALLBACK skia_tls_callback;
+const PIMAGE_TLS_CALLBACK skia_tls_callback = onTLSCallback;
+#pragma const_seg()
+
+#else
+
+#pragma data_seg(".CRT$XLB")
+PIMAGE_TLS_CALLBACK skia_tls_callback = onTLSCallback;
+#pragma data_seg()
+
+#endif
+}
diff --git a/src/sfnt/SkOTTableTypes.h b/src/sfnt/SkOTTableTypes.h
new file mode 100644
index 0000000000..9adec9b81e
--- /dev/null
+++ b/src/sfnt/SkOTTableTypes.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkOTTableTypes_DEFINED
+#define SkOTTableTypes_DEFINED
+
+#include "SkTypes.h"
+#include "SkEndian.h"
+
+//All SK_OT_ prefixed types should be considered as big endian.
+typedef uint8_t SK_OT_BYTE;
+#if CHAR_BIT == 8
+typedef signed char SK_OT_CHAR; //easier to debug
+#else
+typedef int8_t SK_OT_CHAR;
+#endif
+typedef uint16_t SK_OT_SHORT;
+typedef uint16_t SK_OT_USHORT;
+typedef uint32_t SK_OT_ULONG;
+typedef uint32_t SK_OT_LONG;
+//16.16 Signed fixed point representation.
+typedef int32_t SK_OT_Fixed;
+//2.14 Signed fixed point representation.
+typedef uint16_t SK_OT_F2DOT14;
+//F units are the units of measurement in em space.
+typedef uint16_t SK_OT_FWORD;
+typedef uint16_t SK_OT_UFWORD;
+//Number of seconds since 12:00 midnight, January 1, 1904.
+typedef uint64_t SK_OT_LONGDATETIME;
+
+#define SK_OT_BYTE_BITFIELD SK_UINT8_BITFIELD
+
+template<typename T> class SkOTTableTAG {
+public:
+ /**
+ * SkOTTableTAG<T>::value is the big endian value of an OpenType table tag.
+ * It may be directly compared with raw big endian table data.
+ */
+ static const SK_OT_ULONG value = SkTEndian_SwapBE32(
+ SkSetFourByteTag(T::TAG0, T::TAG1, T::TAG2, T::TAG3)
+ );
+};
+
+#endif
diff --git a/src/sfnt/SkOTTable_OS_2.h b/src/sfnt/SkOTTable_OS_2.h
new file mode 100644
index 0000000000..c4dbc5336c
--- /dev/null
+++ b/src/sfnt/SkOTTable_OS_2.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkOTTable_OS_2_DEFINED
+#define SkOTTable_OS_2_DEFINED
+
+#include "SkOTTable_OS_2_VA.h"
+#include "SkOTTable_OS_2_V0.h"
+#include "SkOTTable_OS_2_V1.h"
+#include "SkOTTable_OS_2_V2.h"
+#include "SkOTTable_OS_2_V3.h"
+#include "SkOTTable_OS_2_V4.h"
+
+#pragma pack(push, 1)
+
+struct SkOTTableOS2 {
+ static const SK_OT_CHAR TAG0 = 'O';
+ static const SK_OT_CHAR TAG1 = 'S';
+ static const SK_OT_CHAR TAG2 = '/';
+ static const SK_OT_CHAR TAG3 = '2';
+ static const SK_OT_ULONG TAG = SkOTTableTAG<SkOTTableOS2>::value;
+
+ union Version {
+ SK_OT_USHORT version;
+
+ //original V0 TT
+ struct VA : SkOTTableOS2_VA { } vA;
+ struct V0 : SkOTTableOS2_V0 { } v0;
+ struct V1 : SkOTTableOS2_V1 { } v1;
+ struct V2 : SkOTTableOS2_V2 { } v2;
+ //makes fsType 0-3 exclusive
+ struct V3 : SkOTTableOS2_V3 { } v3;
+ //defines fsSelection bits 7-9
+ struct V4 : SkOTTableOS2_V4 { } v4;
+ } version;
+};
+
+#pragma pack(pop)
+
+
+SK_COMPILE_ASSERT(sizeof(SkOTTableOS2::Version::VA) == 68, sizeof_SkOTTableOS2__VA_not_68);
+SK_COMPILE_ASSERT(sizeof(SkOTTableOS2::Version::V0) == 78, sizeof_SkOTTableOS2__V0_not_78);
+SK_COMPILE_ASSERT(sizeof(SkOTTableOS2::Version::V1) == 86, sizeof_SkOTTableOS2__V1_not_86);
+SK_COMPILE_ASSERT(sizeof(SkOTTableOS2::Version::V2) == 96, sizeof_SkOTTableOS2__V2_not_96);
+SK_COMPILE_ASSERT(sizeof(SkOTTableOS2::Version::V3) == 96, sizeof_SkOTTableOS2__V3_not_96);
+SK_COMPILE_ASSERT(sizeof(SkOTTableOS2::Version::V4) == 96, sizeof_SkOTTableOS2__V4_not_96);
+
+#endif
diff --git a/src/sfnt/SkOTTable_OS_2_V0.h b/src/sfnt/SkOTTable_OS_2_V0.h
new file mode 100644
index 0000000000..14d322f7ce
--- /dev/null
+++ b/src/sfnt/SkOTTable_OS_2_V0.h
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkOTTable_OS_2_V0_DEFINED
+#define SkOTTable_OS_2_V0_DEFINED
+
+#include "SkEndian.h"
+#include "SkIBMFamilyClass.h"
+#include "SkOTTableTypes.h"
+#include "SkPanose.h"
+#include "SkTypedEnum.h"
+
+#pragma pack(push, 1)
+
+struct SkOTTableOS2_V0 {
+ SK_OT_USHORT version;
+ //SkOTTableOS2_VA::VERSION and SkOTTableOS2_V0::VERSION are both 0.
+ //The only way to differentiate these two versions is by the size of the table.
+ static const SK_OT_USHORT VERSION = SkTEndian_SwapBE16(0);
+
+ SK_OT_SHORT xAvgCharWidth;
+ struct WeightClass {
+ SK_TYPED_ENUM(Value, SK_OT_USHORT,
+ ((Thin, SkTEndian_SwapBE16(100)))
+ ((ExtraLight, SkTEndian_SwapBE16(200)))
+ ((Light, SkTEndian_SwapBE16(300)))
+ ((Normal, SkTEndian_SwapBE16(400)))
+ ((Medium, SkTEndian_SwapBE16(500)))
+ ((SemiBold, SkTEndian_SwapBE16(600)))
+ ((Bold, SkTEndian_SwapBE16(700)))
+ ((ExtraBold, SkTEndian_SwapBE16(800)))
+ ((Black, SkTEndian_SwapBE16(900)))
+ SK_SEQ_END,
+ SK_SEQ_END)
+ SK_OT_USHORT value;
+ } usWeightClass;
+ struct WidthClass {
+ SK_TYPED_ENUM(Value, SK_OT_USHORT,
+ ((UltraCondensed, SkTEndian_SwapBE16(1)))
+ ((ExtraCondensed, SkTEndian_SwapBE16(2)))
+ ((Condensed, SkTEndian_SwapBE16(3)))
+ ((SemiCondensed, SkTEndian_SwapBE16(4)))
+ ((Medium, SkTEndian_SwapBE16(5)))
+ ((SemiExpanded, SkTEndian_SwapBE16(6)))
+ ((Expanded, SkTEndian_SwapBE16(7)))
+ ((ExtraExpanded, SkTEndian_SwapBE16(8)))
+ ((UltraExpanded, SkTEndian_SwapBE16(9)))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } usWidthClass;
+ union Type {
+ struct Field {
+ //8-15
+ SK_OT_BYTE_BITFIELD(
+ Reserved08,
+ Reserved09,
+ Reserved10,
+ Reserved11,
+ Reserved12,
+ Reserved13,
+ Reserved14,
+ Reserved15)
+ //0-7
+ SK_OT_BYTE_BITFIELD(
+ Reserved00,
+ Restricted,
+ PreviewPrint,
+ Editable,
+ Reserved04,
+ Reserved05,
+ Reserved06,
+ Reserved07)
+ } field;
+ struct Raw {
+ static const SK_OT_USHORT Installable = SkTEndian_SwapBE16(0);
+ static const SK_OT_USHORT RestrictedMask = SkTEndian_SwapBE16(1 << 1);
+ static const SK_OT_USHORT PreviewPrintMask = SkTEndian_SwapBE16(1 << 2);
+ static const SK_OT_USHORT EditableMask = SkTEndian_SwapBE16(1 << 3);
+ SK_OT_USHORT value;
+ } raw;
+ } fsType;
+ SK_OT_SHORT ySubscriptXSize;
+ SK_OT_SHORT ySubscriptYSize;
+ SK_OT_SHORT ySubscriptXOffset;
+ SK_OT_SHORT ySubscriptYOffset;
+ SK_OT_SHORT ySuperscriptXSize;
+ SK_OT_SHORT ySuperscriptYSize;
+ SK_OT_SHORT ySuperscriptXOffset;
+ SK_OT_SHORT ySuperscriptYOffset;
+ SK_OT_SHORT yStrikeoutSize;
+ SK_OT_SHORT yStrikeoutPosition;
+ SkIBMFamilyClass sFamilyClass;
+ SkPanose panose;
+ SK_OT_ULONG ulCharRange[4];
+ SK_OT_CHAR achVendID[4];
+ union Selection {
+ struct Field {
+ //8-15
+ SK_OT_BYTE_BITFIELD(
+ Reserved08,
+ Reserved09,
+ Reserved10,
+ Reserved11,
+ Reserved12,
+ Reserved13,
+ Reserved14,
+ Reserved15)
+ //0-7
+ SK_OT_BYTE_BITFIELD(
+ Italic,
+ Underscore,
+ Negative,
+ Outlined,
+ Strikeout,
+ Bold,
+ Regular,
+ Reserved07)
+ } field;
+ struct Raw {
+ static const SK_OT_USHORT ItalicMask = SkTEndian_SwapBE16(1 << 0);
+ static const SK_OT_USHORT UnderscoreMask = SkTEndian_SwapBE16(1 << 1);
+ static const SK_OT_USHORT NegativeMask = SkTEndian_SwapBE16(1 << 2);
+ static const SK_OT_USHORT OutlinedMask = SkTEndian_SwapBE16(1 << 3);
+ static const SK_OT_USHORT StrikeoutMask = SkTEndian_SwapBE16(1 << 4);
+ static const SK_OT_USHORT BoldMask = SkTEndian_SwapBE16(1 << 5);
+ static const SK_OT_USHORT RegularMask = SkTEndian_SwapBE16(1 << 6);
+ SK_OT_USHORT value;
+ } raw;
+ } fsSelection;
+ SK_OT_USHORT usFirstCharIndex;
+ SK_OT_USHORT usLastCharIndex;
+ //version0
+ SK_OT_SHORT sTypoAscender;
+ SK_OT_SHORT sTypoDescender;
+ SK_OT_SHORT sTypoLineGap;
+ SK_OT_USHORT usWinAscent;
+ SK_OT_USHORT usWinDescent;
+};
+
+#pragma pack(pop)
+
+
+SK_COMPILE_ASSERT(sizeof(SkOTTableOS2_V0) == 78, sizeof_SkOTTableOS2_V0_not_78);
+
+#endif
diff --git a/src/sfnt/SkOTTable_OS_2_V1.h b/src/sfnt/SkOTTable_OS_2_V1.h
new file mode 100644
index 0000000000..52a60c0a4d
--- /dev/null
+++ b/src/sfnt/SkOTTable_OS_2_V1.h
@@ -0,0 +1,518 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkOTTable_OS_2_V1_DEFINED
+#define SkOTTable_OS_2_V1_DEFINED
+
+#include "SkEndian.h"
+#include "SkIBMFamilyClass.h"
+#include "SkOTTableTypes.h"
+#include "SkPanose.h"
+#include "SkTypedEnum.h"
+
+#pragma pack(push, 1)
+
+struct SkOTTableOS2_V1 {
+ SK_OT_USHORT version;
+ static const SK_OT_USHORT VERSION = SkTEndian_SwapBE16(1);
+
+ SK_OT_SHORT xAvgCharWidth;
+ struct WeightClass {
+ SK_TYPED_ENUM(Value, SK_OT_USHORT,
+ ((Thin, SkTEndian_SwapBE16(100)))
+ ((ExtraLight, SkTEndian_SwapBE16(200)))
+ ((Light, SkTEndian_SwapBE16(300)))
+ ((Normal, SkTEndian_SwapBE16(400)))
+ ((Medium, SkTEndian_SwapBE16(500)))
+ ((SemiBold, SkTEndian_SwapBE16(600)))
+ ((Bold, SkTEndian_SwapBE16(700)))
+ ((ExtraBold, SkTEndian_SwapBE16(800)))
+ ((Black, SkTEndian_SwapBE16(900)))
+ SK_SEQ_END,
+ SK_SEQ_END)
+ SK_OT_USHORT value;
+ } usWeightClass;
+ struct WidthClass {
+ SK_TYPED_ENUM(Value, SK_OT_USHORT,
+ ((UltraCondensed, SkTEndian_SwapBE16(1)))
+ ((ExtraCondensed, SkTEndian_SwapBE16(2)))
+ ((Condensed, SkTEndian_SwapBE16(3)))
+ ((SemiCondensed, SkTEndian_SwapBE16(4)))
+ ((Medium, SkTEndian_SwapBE16(5)))
+ ((SemiExpanded, SkTEndian_SwapBE16(6)))
+ ((Expanded, SkTEndian_SwapBE16(7)))
+ ((ExtraExpanded, SkTEndian_SwapBE16(8)))
+ ((UltraExpanded, SkTEndian_SwapBE16(9)))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } usWidthClass;
+ union Type {
+ struct Field {
+ //8-15
+ SK_OT_BYTE_BITFIELD(
+ Reserved08,
+ Reserved09,
+ Reserved10,
+ Reserved11,
+ Reserved12,
+ Reserved13,
+ Reserved14,
+ Reserved15)
+ //0-7
+ SK_OT_BYTE_BITFIELD(
+ Reserved00,
+ Restricted,
+ PreviewPrint,
+ Editable,
+ Reserved04,
+ Reserved05,
+ Reserved06,
+ Reserved07)
+ } field;
+ struct Raw {
+ static const SK_OT_USHORT Installable = SkTEndian_SwapBE16(0);
+ static const SK_OT_USHORT RestrictedMask = SkTEndian_SwapBE16(1 << 1);
+ static const SK_OT_USHORT PreviewPrintMask = SkTEndian_SwapBE16(1 << 2);
+ static const SK_OT_USHORT EditableMask = SkTEndian_SwapBE16(1 << 3);
+ SK_OT_USHORT value;
+ } raw;
+ } fsType;
+ SK_OT_SHORT ySubscriptXSize;
+ SK_OT_SHORT ySubscriptYSize;
+ SK_OT_SHORT ySubscriptXOffset;
+ SK_OT_SHORT ySubscriptYOffset;
+ SK_OT_SHORT ySuperscriptXSize;
+ SK_OT_SHORT ySuperscriptYSize;
+ SK_OT_SHORT ySuperscriptXOffset;
+ SK_OT_SHORT ySuperscriptYOffset;
+ SK_OT_SHORT yStrikeoutSize;
+ SK_OT_SHORT yStrikeoutPosition;
+ SkIBMFamilyClass sFamilyClass;
+ SkPanose panose;
+ union UnicodeRange {
+ struct Field {
+ //l0 24-31
+ SK_OT_BYTE_BITFIELD(
+ Thai,
+ Lao,
+ BasicGeorgian,
+ GeorgianExtended,
+ HangulJamo,
+ LatinExtendedAdditional,
+ GreekExtended,
+ GeneralPunctuation)
+ //l0 16-23
+ SK_OT_BYTE_BITFIELD(
+ Bengali,
+ Gurmukhi,
+ Gujarati,
+ Oriya,
+ Tamil,
+ Telugu,
+ Kannada,
+ Malayalam)
+ //l0 8-15
+ SK_OT_BYTE_BITFIELD(
+ GreekSymbolsAndCoptic,
+ Cyrillic,
+ Armenian,
+ BasicHebrew,
+ HebrewExtendedAB,
+ BasicArabic,
+ ArabicExtended,
+ Devanagari)
+ //l0 0-7
+ SK_OT_BYTE_BITFIELD(
+ BasicLatin,
+ Latin1Supplement,
+ LatinExtendedA,
+ LatinExtendedB,
+ IPAExtensions,
+ SpacingModifierLetters,
+ CombiningDiacriticalMarks,
+ BasicGreek)
+
+ //l1 24-31
+ SK_OT_BYTE_BITFIELD(
+ Hangul,
+ Reserved057,
+ Reserved058,
+ CJKUnifiedIdeographs,
+ PrivateUseArea,
+ CJKCompatibilityIdeographs,
+ AlphabeticPresentationForms,
+ ArabicPresentationFormsA)
+ //l1 16-23
+ SK_OT_BYTE_BITFIELD(
+ CJKSymbolsAndPunctuation,
+ Hiragana,
+ Katakana,
+ Bopomofo,
+ HangulCompatibilityJamo,
+ CJKMiscellaneous,
+ EnclosedCJKLettersAndMonths,
+ CJKCompatibility)
+ //l1 8-15
+ SK_OT_BYTE_BITFIELD(
+ ControlPictures,
+ OpticalCharacterRecognition,
+ EnclosedAlphanumerics,
+ BoxDrawing,
+ BlockElements,
+ GeometricShapes,
+ MiscellaneousSymbols,
+ Dingbats)
+ //l1 0-7
+ SK_OT_BYTE_BITFIELD(
+ SuperscriptsAndSubscripts,
+ CurrencySymbols,
+ CombiningDiacriticalMarksForSymbols,
+ LetterlikeSymbols,
+ NumberForms,
+ Arrows,
+ MathematicalOperators,
+ MiscellaneousTechnical)
+
+ //l2 24-31
+ SK_OT_BYTE_BITFIELD(
+ Reserved088,
+ Reserved089,
+ Reserved090,
+ Reserved091,
+ Reserved092,
+ Reserved093,
+ Reserved094,
+ Reserved095)
+ //l2 16-23
+ SK_OT_BYTE_BITFIELD(
+ Reserved080,
+ Reserved081,
+ Reserved082,
+ Reserved083,
+ Reserved084,
+ Reserved085,
+ Reserved086,
+ Reserved087)
+ //l2 8-15
+ SK_OT_BYTE_BITFIELD(
+ Reserved072,
+ Reserved073,
+ Reserved074,
+ Reserved075,
+ Reserved076,
+ Reserved077,
+ Reserved078,
+ Reserved079)
+ //l2 0-7
+ SK_OT_BYTE_BITFIELD(
+ CombiningHalfMarks,
+ CJKCompatibilityForms,
+ SmallFormVariants,
+ ArabicPresentationFormsB,
+ HalfwidthAndFullwidthForms,
+ Specials,
+ Reserved70,
+ Reserved71)
+
+ //l3 24-31
+ SK_OT_BYTE_BITFIELD(
+ Reserved120,
+ Reserved121,
+ Reserved122,
+ Reserved123,
+ Reserved124,
+ Reserved125,
+ Reserved126,
+ Reserved127)
+ //l3 16-23
+ SK_OT_BYTE_BITFIELD(
+ Reserved112,
+ Reserved113,
+ Reserved114,
+ Reserved115,
+ Reserved116,
+ Reserved117,
+ Reserved118,
+ Reserved119)
+ //l3 8-15
+ SK_OT_BYTE_BITFIELD(
+ Reserved104,
+ Reserved105,
+ Reserved106,
+ Reserved107,
+ Reserved108,
+ Reserved109,
+ Reserved110,
+ Reserved111)
+ //l3 0-7
+ SK_OT_BYTE_BITFIELD(
+ Reserved096,
+ Reserved097,
+ Reserved098,
+ Reserved099,
+ Reserved100,
+ Reserved101,
+ Reserved102,
+ Reserved103)
+ } field;
+ struct Raw {
+ struct l0 {
+ static const SK_OT_ULONG BasicLatinMask = SkTEndian_SwapBE32(1 << 0);
+ static const SK_OT_ULONG Latin1SupplementMask = SkTEndian_SwapBE32(1 << 1);
+ static const SK_OT_ULONG LatinExtendedAMask = SkTEndian_SwapBE32(1 << 2);
+ static const SK_OT_ULONG LatinExtendedBMask = SkTEndian_SwapBE32(1 << 3);
+ static const SK_OT_ULONG IPAExtensionsMask = SkTEndian_SwapBE32(1 << 4);
+ static const SK_OT_ULONG SpacingModifierLettersMask = SkTEndian_SwapBE32(1 << 5);
+ static const SK_OT_ULONG CombiningDiacriticalMarksMask = SkTEndian_SwapBE32(1 << 6);
+ static const SK_OT_ULONG BasicGreekMask = SkTEndian_SwapBE32(1 << 7);
+ static const SK_OT_ULONG GreekSymbolsAndCCopticMask = SkTEndian_SwapBE32(1 << 8);
+ static const SK_OT_ULONG CyrillicMask = SkTEndian_SwapBE32(1 << 9);
+ static const SK_OT_ULONG ArmenianMask = SkTEndian_SwapBE32(1 << 10);
+ static const SK_OT_ULONG BasicHebrewMask = SkTEndian_SwapBE32(1 << 11);
+ static const SK_OT_ULONG HebrewExtendedABMask = SkTEndian_SwapBE32(1 << 12);
+ static const SK_OT_ULONG BasicArabicMask = SkTEndian_SwapBE32(1 << 13);
+ static const SK_OT_ULONG ArabicExtendedMask = SkTEndian_SwapBE32(1 << 14);
+ static const SK_OT_ULONG DevanagariMask = SkTEndian_SwapBE32(1 << 15);
+ static const SK_OT_ULONG BengaliMask = SkTEndian_SwapBE32(1 << 16);
+ static const SK_OT_ULONG GurmukhiMask = SkTEndian_SwapBE32(1 << 17);
+ static const SK_OT_ULONG GujaratiMask = SkTEndian_SwapBE32(1 << 18);
+ static const SK_OT_ULONG OriyaMask = SkTEndian_SwapBE32(1 << 19);
+ static const SK_OT_ULONG TamilMask = SkTEndian_SwapBE32(1 << 20);
+ static const SK_OT_ULONG TeluguMask = SkTEndian_SwapBE32(1 << 21);
+ static const SK_OT_ULONG KannadaMask = SkTEndian_SwapBE32(1 << 22);
+ static const SK_OT_ULONG MalayalamMask = SkTEndian_SwapBE32(1 << 23);
+ static const SK_OT_ULONG ThaiMask = SkTEndian_SwapBE32(1 << 24);
+ static const SK_OT_ULONG LaoMask = SkTEndian_SwapBE32(1 << 25);
+ static const SK_OT_ULONG BasicGeorgianMask = SkTEndian_SwapBE32(1 << 26);
+ static const SK_OT_ULONG GeorgianExtendedMask = SkTEndian_SwapBE32(1 << 27);
+ static const SK_OT_ULONG HangulJamoMask = SkTEndian_SwapBE32(1 << 28);
+ static const SK_OT_ULONG LatinExtendedAdditionalMask = SkTEndian_SwapBE32(1 << 29);
+ static const SK_OT_ULONG GreekExtendedMask = SkTEndian_SwapBE32(1 << 30);
+ static const SK_OT_ULONG GeneralPunctuationMask = SkTEndian_SwapBE32(1 << 31);
+ };
+ struct l1 {
+ static const SK_OT_ULONG SuperscriptsAndSubscriptsMask = SkTEndian_SwapBE32(1 << (32 - 32));
+ static const SK_OT_ULONG CurrencySymbolsMask = SkTEndian_SwapBE32(1 << (33 - 32));
+ static const SK_OT_ULONG CombiningDiacriticalMarksForSymbolsMask = SkTEndian_SwapBE32(1 << (34 - 32));
+ static const SK_OT_ULONG LetterlikeSymbolsMask = SkTEndian_SwapBE32(1 << (35 - 32));
+ static const SK_OT_ULONG NumberFormsMask = SkTEndian_SwapBE32(1 << (36 - 32));
+ static const SK_OT_ULONG ArrowsMask = SkTEndian_SwapBE32(1 << (37 - 32));
+ static const SK_OT_ULONG MathematicalOperatorsMask = SkTEndian_SwapBE32(1 << (38 - 32));
+ static const SK_OT_ULONG MiscellaneousTechnicalMask = SkTEndian_SwapBE32(1 << (39 - 32));
+ static const SK_OT_ULONG ControlPicturesMask = SkTEndian_SwapBE32(1 << (40 - 32));
+ static const SK_OT_ULONG OpticalCharacterRecognitionMask = SkTEndian_SwapBE32(1 << (41 - 32));
+ static const SK_OT_ULONG EnclosedAlphanumericsMask = SkTEndian_SwapBE32(1 << (42 - 32));
+ static const SK_OT_ULONG BoxDrawingMask = SkTEndian_SwapBE32(1 << (43 - 32));
+ static const SK_OT_ULONG BlockElementsMask = SkTEndian_SwapBE32(1 << (44 - 32));
+ static const SK_OT_ULONG GeometricShapesMask = SkTEndian_SwapBE32(1 << (45 - 32));
+ static const SK_OT_ULONG MiscellaneousSymbolsMask = SkTEndian_SwapBE32(1 << (46 - 32));
+ static const SK_OT_ULONG DingbatsMask = SkTEndian_SwapBE32(1 << (47 - 32));
+ static const SK_OT_ULONG CJKSymbolsAndPunctuationMask = SkTEndian_SwapBE32(1 << (48 - 32));
+ static const SK_OT_ULONG HiraganaMask = SkTEndian_SwapBE32(1 << (49 - 32));
+ static const SK_OT_ULONG KatakanaMask = SkTEndian_SwapBE32(1 << (50 - 32));
+ static const SK_OT_ULONG BopomofoMask = SkTEndian_SwapBE32(1 << (51 - 32));
+ static const SK_OT_ULONG HangulCompatibilityJamoMask = SkTEndian_SwapBE32(1 << (52 - 32));
+ static const SK_OT_ULONG CJKMiscellaneousMask = SkTEndian_SwapBE32(1 << (53 - 32));
+ static const SK_OT_ULONG EnclosedCJKLettersAndMonthsMask = SkTEndian_SwapBE32(1 << (54 - 32));
+ static const SK_OT_ULONG CJKCompatibilityMask = SkTEndian_SwapBE32(1 << (55 - 32));
+ static const SK_OT_ULONG HangulMask = SkTEndian_SwapBE32(1 << (56 - 32));
+ //Reserved
+ //Reserved
+ static const SK_OT_ULONG CJKUnifiedIdeographsMask = SkTEndian_SwapBE32(1 << (59 - 32));
+ static const SK_OT_ULONG PrivateUseAreaMask = SkTEndian_SwapBE32(1 << (60 - 32));
+ static const SK_OT_ULONG CJKCompatibilityIdeographsMask = SkTEndian_SwapBE32(1 << (61 - 32));
+ static const SK_OT_ULONG AlphabeticPresentationFormsMask = SkTEndian_SwapBE32(1 << (62 - 32));
+ static const SK_OT_ULONG ArabicPresentationFormsAMask = SkTEndian_SwapBE32(1 << (63 - 32));
+ };
+ struct l2 {
+ static const SK_OT_ULONG CombiningHalfMarksMask = SkTEndian_SwapBE32(1 << (64 - 64));
+ static const SK_OT_ULONG CJKCompatibilityFormsMask = SkTEndian_SwapBE32(1 << (65 - 64));
+ static const SK_OT_ULONG SmallFormVariantsMask = SkTEndian_SwapBE32(1 << (66 - 64));
+ static const SK_OT_ULONG ArabicPresentationFormsBMask = SkTEndian_SwapBE32(1 << (67 - 64));
+ static const SK_OT_ULONG HalfwidthAndFullwidthFormsMask = SkTEndian_SwapBE32(1 << (68 - 64));
+ static const SK_OT_ULONG SpecialsMask = SkTEndian_SwapBE32(1 << (69 - 64));
+ };
+ SK_OT_ULONG value[4];
+ } raw;
+ } ulUnicodeRange;
+ SK_OT_CHAR achVendID[4];
+ union Selection {
+ struct Field {
+ //8-15
+ SK_OT_BYTE_BITFIELD(
+ Reserved08,
+ Reserved09,
+ Reserved10,
+ Reserved11,
+ Reserved12,
+ Reserved13,
+ Reserved14,
+ Reserved15)
+ //0-7
+ SK_OT_BYTE_BITFIELD(
+ Italic,
+ Underscore,
+ Negative,
+ Outlined,
+ Strikeout,
+ Bold,
+ Regular,
+ Reserved07)
+ } field;
+ struct Raw {
+ static const SK_OT_USHORT ItalicMask = SkTEndian_SwapBE16(1 << 0);
+ static const SK_OT_USHORT UnderscoreMask = SkTEndian_SwapBE16(1 << 1);
+ static const SK_OT_USHORT NegativeMask = SkTEndian_SwapBE16(1 << 2);
+ static const SK_OT_USHORT OutlinedMask = SkTEndian_SwapBE16(1 << 3);
+ static const SK_OT_USHORT StrikeoutMask = SkTEndian_SwapBE16(1 << 4);
+ static const SK_OT_USHORT BoldMask = SkTEndian_SwapBE16(1 << 5);
+ static const SK_OT_USHORT RegularMask = SkTEndian_SwapBE16(1 << 6);
+ SK_OT_USHORT value;
+ } raw;
+ } fsSelection;
+ SK_OT_USHORT usFirstCharIndex;
+ SK_OT_USHORT usLastCharIndex;
+ //version0
+ SK_OT_SHORT sTypoAscender;
+ SK_OT_SHORT sTypoDescender;
+ SK_OT_SHORT sTypoLineGap;
+ SK_OT_USHORT usWinAscent;
+ SK_OT_USHORT usWinDescent;
+ //version1
+ union CodePageRange {
+ struct Field {
+ //l0 24-31
+ SK_OT_BYTE_BITFIELD(
+ Reserved24,
+ Reserved25,
+ Reserved26,
+ Reserved27,
+ Reserved28,
+ MacintoshCharacterSet,
+ OEMCharacterSet,
+ SymbolCharacterSet)
+ //l0 16-23
+ SK_OT_BYTE_BITFIELD(
+ Thai_874,
+ JISJapan_932,
+ ChineseSimplified_936,
+ KoreanWansung_949,
+ ChineseTraditional_950,
+ KoreanJohab_1361,
+ Reserved22,
+ Reserved23)
+ //l0 8-15
+ SK_OT_BYTE_BITFIELD(
+ Reserved08,
+ Reserved09,
+ Reserved10,
+ Reserved11,
+ Reserved12,
+ Reserved13,
+ Reserved14,
+ Reserved15)
+ //l0 0-7
+ SK_OT_BYTE_BITFIELD(
+ Latin1_1252,
+ Latin2EasternEurope_1250,
+ Cyrillic_1251,
+ Greek_1253,
+ Turkish_1254,
+ Hebrew_1255,
+ Arabic_1256,
+ WindowsBaltic_1257)
+
+ //l1 24-31
+ SK_OT_BYTE_BITFIELD(
+ IBMTurkish_857,
+ IBMCyrillic_855,
+ Latin2_852,
+ MSDOSBaltic_775,
+ Greek_737,
+ Arabic_708,
+ WELatin1_850,
+ US_437)
+ //l1 16-23
+ SK_OT_BYTE_BITFIELD(
+ IBMGreek_869,
+ MSDOSRussian_866,
+ MSDOSNordic_865,
+ Arabic_864,
+ MSDOSCanadianFrench_863,
+ Hebrew_862,
+ MSDOSIcelandic_861,
+ MSDOSPortuguese_860)
+ //l1 8-15
+ SK_OT_BYTE_BITFIELD(
+ Reserved40,
+ Reserved41,
+ Reserved42,
+ Reserved43,
+ Reserved44,
+ Reserved45,
+ Reserved46,
+ Reserved47)
+ //l1 0-7
+ SK_OT_BYTE_BITFIELD(
+ Reserved32,
+ Reserved33,
+ Reserved34,
+ Reserved35,
+ Reserved36,
+ Reserved37,
+ Reserved38,
+ Reserved39)
+ } field;
+ struct Raw {
+ struct l0 {
+ static const SK_OT_ULONG Latin1_1252Mask = SkTEndian_SwapBE32(1 << 0);
+ static const SK_OT_ULONG Latin2EasternEurope_1250Mask = SkTEndian_SwapBE32(1 << 1);
+ static const SK_OT_ULONG Cyrillic_1251Mask = SkTEndian_SwapBE32(1 << 2);
+ static const SK_OT_ULONG Greek_1253Mask = SkTEndian_SwapBE32(1 << 3);
+ static const SK_OT_ULONG Turkish_1254Mask = SkTEndian_SwapBE32(1 << 4);
+ static const SK_OT_ULONG Hebrew_1255Mask = SkTEndian_SwapBE32(1 << 5);
+ static const SK_OT_ULONG Arabic_1256Mask = SkTEndian_SwapBE32(1 << 6);
+ static const SK_OT_ULONG WindowsBaltic_1257Mask = SkTEndian_SwapBE32(1 << 7);
+ static const SK_OT_ULONG Thai_874Mask = SkTEndian_SwapBE32(1 << 16);
+ static const SK_OT_ULONG JISJapan_932Mask = SkTEndian_SwapBE32(1 << 17);
+ static const SK_OT_ULONG ChineseSimplified_936Mask = SkTEndian_SwapBE32(1 << 18);
+ static const SK_OT_ULONG KoreanWansung_949Mask = SkTEndian_SwapBE32(1 << 19);
+ static const SK_OT_ULONG ChineseTraditional_950Mask = SkTEndian_SwapBE32(1 << 20);
+ static const SK_OT_ULONG KoreanJohab_1361Mask = SkTEndian_SwapBE32(1 << 21);
+ static const SK_OT_ULONG MacintoshCharacterSetMask = SkTEndian_SwapBE32(1 << 29);
+ static const SK_OT_ULONG OEMCharacterSetMask = SkTEndian_SwapBE32(1 << 30);
+ static const SK_OT_ULONG SymbolCharacterSetMask = SkTEndian_SwapBE32(1 << 31);
+ };
+ struct l1 {
+ static const SK_OT_ULONG IBMGreek_869Mask = SkTEndian_SwapBE32(1 << (48 - 32));
+ static const SK_OT_ULONG MSDOSRussian_866Mask = SkTEndian_SwapBE32(1 << (49 - 32));
+ static const SK_OT_ULONG MSDOSNordic_865Mask = SkTEndian_SwapBE32(1 << (50 - 32));
+ static const SK_OT_ULONG Arabic_864Mask = SkTEndian_SwapBE32(1 << (51 - 32));
+ static const SK_OT_ULONG MSDOSCanadianFrench_863Mask = SkTEndian_SwapBE32(1 << (52 - 32));
+ static const SK_OT_ULONG Hebrew_862Mask = SkTEndian_SwapBE32(1 << (53 - 32));
+ static const SK_OT_ULONG MSDOSIcelandic_861Mask = SkTEndian_SwapBE32(1 << (54 - 32));
+ static const SK_OT_ULONG MSDOSPortuguese_860Mask = SkTEndian_SwapBE32(1 << (55 - 32));
+ static const SK_OT_ULONG IBMTurkish_857Mask = SkTEndian_SwapBE32(1 << (56 - 32));
+ static const SK_OT_ULONG IBMCyrillic_855Mask = SkTEndian_SwapBE32(1 << (57 - 32));
+ static const SK_OT_ULONG Latin2_852Mask = SkTEndian_SwapBE32(1 << (58 - 32));
+ static const SK_OT_ULONG MSDOSBaltic_775Mask = SkTEndian_SwapBE32(1 << (59 - 32));
+ static const SK_OT_ULONG Greek_737Mask = SkTEndian_SwapBE32(1 << (60 - 32));
+ static const SK_OT_ULONG Arabic_708Mask = SkTEndian_SwapBE32(1 << (61 - 32));
+ static const SK_OT_ULONG WELatin1_850Mask = SkTEndian_SwapBE32(1 << (62 - 32));
+ static const SK_OT_ULONG US_437Mask = SkTEndian_SwapBE32(1 << (63 - 32));
+ };
+ SK_OT_ULONG value[2];
+ } raw;
+ } ulCodePageRange;
+};
+
+#pragma pack(pop)
+
+
+SK_COMPILE_ASSERT(sizeof(SkOTTableOS2_V1) == 86, sizeof_SkOTTableOS2_V1_not_86);
+
+#endif
diff --git a/src/sfnt/SkOTTable_OS_2_V2.h b/src/sfnt/SkOTTable_OS_2_V2.h
new file mode 100644
index 0000000000..4be2e199a6
--- /dev/null
+++ b/src/sfnt/SkOTTable_OS_2_V2.h
@@ -0,0 +1,540 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkOTTable_OS_2_V2_DEFINED
+#define SkOTTable_OS_2_V2_DEFINED
+
+#include "SkEndian.h"
+#include "SkIBMFamilyClass.h"
+#include "SkOTTableTypes.h"
+#include "SkPanose.h"
+#include "SkTypedEnum.h"
+
+#pragma pack(push, 1)
+
+struct SkOTTableOS2_V2 {
+ SK_OT_USHORT version;
+ static const SK_OT_USHORT VERSION = SkTEndian_SwapBE16(2);
+
+ SK_OT_SHORT xAvgCharWidth;
+ struct WeightClass {
+ SK_TYPED_ENUM(Value, SK_OT_USHORT,
+ ((Thin, SkTEndian_SwapBE16(100)))
+ ((ExtraLight, SkTEndian_SwapBE16(200)))
+ ((Light, SkTEndian_SwapBE16(300)))
+ ((Normal, SkTEndian_SwapBE16(400)))
+ ((Medium, SkTEndian_SwapBE16(500)))
+ ((SemiBold, SkTEndian_SwapBE16(600)))
+ ((Bold, SkTEndian_SwapBE16(700)))
+ ((ExtraBold, SkTEndian_SwapBE16(800)))
+ ((Black, SkTEndian_SwapBE16(900)))
+ SK_SEQ_END,
+ SK_SEQ_END)
+ SK_OT_USHORT value;
+ } usWeightClass;
+ struct WidthClass {
+ SK_TYPED_ENUM(Value, SK_OT_USHORT,
+ ((UltraCondensed, SkTEndian_SwapBE16(1)))
+ ((ExtraCondensed, SkTEndian_SwapBE16(2)))
+ ((Condensed, SkTEndian_SwapBE16(3)))
+ ((SemiCondensed, SkTEndian_SwapBE16(4)))
+ ((Medium, SkTEndian_SwapBE16(5)))
+ ((SemiExpanded, SkTEndian_SwapBE16(6)))
+ ((Expanded, SkTEndian_SwapBE16(7)))
+ ((ExtraExpanded, SkTEndian_SwapBE16(8)))
+ ((UltraExpanded, SkTEndian_SwapBE16(9))),
+ (value)SK_SEQ_END)
+ } usWidthClass;
+ union Type {
+ struct Field {
+ //8-15
+ SK_OT_BYTE_BITFIELD(
+ NoSubsetting,
+ Bitmap,
+ Reserved10,
+ Reserved11,
+ Reserved12,
+ Reserved13,
+ Reserved14,
+ Reserved15)
+ //0-7
+ SK_OT_BYTE_BITFIELD(
+ Reserved00,
+ Restricted,
+ PreviewPrint,
+ Editable,
+ Reserved04,
+ Reserved05,
+ Reserved06,
+ Reserved07)
+ } field;
+ struct Raw {
+ static const SK_OT_USHORT Installable = SkTEndian_SwapBE16(0);
+ static const SK_OT_USHORT RestrictedMask = SkTEndian_SwapBE16(1 << 1);
+ static const SK_OT_USHORT PreviewPrintMask = SkTEndian_SwapBE16(1 << 2);
+ static const SK_OT_USHORT EditableMask = SkTEndian_SwapBE16(1 << 3);
+ static const SK_OT_USHORT NoSubsettingMask = SkTEndian_SwapBE16(1 << 8);
+ static const SK_OT_USHORT BitmapMask = SkTEndian_SwapBE16(1 << 9);
+ SK_OT_USHORT value;
+ } raw;
+ } fsType;
+ SK_OT_SHORT ySubscriptXSize;
+ SK_OT_SHORT ySubscriptYSize;
+ SK_OT_SHORT ySubscriptXOffset;
+ SK_OT_SHORT ySubscriptYOffset;
+ SK_OT_SHORT ySuperscriptXSize;
+ SK_OT_SHORT ySuperscriptYSize;
+ SK_OT_SHORT ySuperscriptXOffset;
+ SK_OT_SHORT ySuperscriptYOffset;
+ SK_OT_SHORT yStrikeoutSize;
+ SK_OT_SHORT yStrikeoutPosition;
+ SkIBMFamilyClass sFamilyClass;
+ SkPanose panose;
+ union UnicodeRange {
+ struct Field {
+ //l0 24-31
+ SK_OT_BYTE_BITFIELD(
+ Thai,
+ Lao,
+ Georgian,
+ Reserved027,
+ HangulJamo,
+ LatinExtendedAdditional,
+ GreekExtended,
+ GeneralPunctuation)
+ //l0 16-23
+ SK_OT_BYTE_BITFIELD(
+ Bengali,
+ Gurmukhi,
+ Gujarati,
+ Oriya,
+ Tamil,
+ Telugu,
+ Kannada,
+ Malayalam)
+ //l0 8-15
+ SK_OT_BYTE_BITFIELD(
+ Reserved008,
+ Cyrillic,
+ Armenian,
+ Hebrew,
+ Reserved012,
+ Arabic,
+ Reserved014,
+ Devanagari)
+ //l0 0-7
+ SK_OT_BYTE_BITFIELD(
+ BasicLatin,
+ Latin1Supplement,
+ LatinExtendedA,
+ LatinExtendedB,
+ IPAExtensions,
+ SpacingModifierLetters,
+ CombiningDiacriticalMarks,
+ Greek)
+
+ //l1 24-31
+ SK_OT_BYTE_BITFIELD(
+ Hangul,
+ Surrogates,
+ Reserved058,
+ CJKUnifiedIdeographs,
+ PrivateUseArea,
+ CJKCompatibilityIdeographs,
+ AlphabeticPresentationForms,
+ ArabicPresentationFormsA)
+ //l1 16-23
+ SK_OT_BYTE_BITFIELD(
+ CJKSymbolsAndPunctuation,
+ Hiragana,
+ Katakana,
+ Bopomofo,
+ HangulCompatibilityJamo,
+ CJKMiscellaneous,
+ EnclosedCJKLettersAndMonths,
+ CJKCompatibility)
+ //l1 8-15
+ SK_OT_BYTE_BITFIELD(
+ ControlPictures,
+ OpticalCharacterRecognition,
+ EnclosedAlphanumerics,
+ BoxDrawing,
+ BlockElements,
+ GeometricShapes,
+ MiscellaneousSymbols,
+ Dingbats)
+ //l1 0-7
+ SK_OT_BYTE_BITFIELD(
+ SuperscriptsAndSubscripts,
+ CurrencySymbols,
+ CombiningDiacriticalMarksForSymbols,
+ LetterlikeSymbols,
+ NumberForms,
+ Arrows,
+ MathematicalOperators,
+ MiscellaneousTechnical)
+
+ //l2 24-31
+ SK_OT_BYTE_BITFIELD(
+ Reserved088,
+ Reserved089,
+ Reserved090,
+ Reserved091,
+ Reserved092,
+ Reserved093,
+ Reserved094,
+ Reserved095)
+ //l2 16-23
+ SK_OT_BYTE_BITFIELD(
+ Khmer,
+ Mongolian,
+ Braille,
+ Yi,
+ Reserved084,
+ Reserved085,
+ Reserved086,
+ Reserved087)
+ //l2 8-15
+ SK_OT_BYTE_BITFIELD(
+ Thaana,
+ Sinhala,
+ Myanmar,
+ Ethiopic,
+ Cherokee,
+ UnifiedCanadianSyllabics,
+ Ogham,
+ Runic)
+ //l2 0-7
+ SK_OT_BYTE_BITFIELD(
+ CombiningHalfMarks,
+ CJKCompatibilityForms,
+ SmallFormVariants,
+ ArabicPresentationFormsB,
+ HalfwidthAndFullwidthForms,
+ Specials,
+ Tibetan,
+ Syriac)
+
+ //l3 24-31
+ SK_OT_BYTE_BITFIELD(
+ Reserved120,
+ Reserved121,
+ Reserved122,
+ Reserved123,
+ Reserved124,
+ Reserved125,
+ Reserved126,
+ Reserved127)
+ //l3 16-23
+ SK_OT_BYTE_BITFIELD(
+ Reserved112,
+ Reserved113,
+ Reserved114,
+ Reserved115,
+ Reserved116,
+ Reserved117,
+ Reserved118,
+ Reserved119)
+ //l3 8-15
+ SK_OT_BYTE_BITFIELD(
+ Reserved104,
+ Reserved105,
+ Reserved106,
+ Reserved107,
+ Reserved108,
+ Reserved109,
+ Reserved110,
+ Reserved111)
+ //l3 0-7
+ SK_OT_BYTE_BITFIELD(
+ Reserved096,
+ Reserved097,
+ Reserved098,
+ Reserved099,
+ Reserved100,
+ Reserved101,
+ Reserved102,
+ Reserved103)
+ } field;
+ struct Raw {
+ struct l0 {
+ static const SK_OT_ULONG BasicLatinMask = SkTEndian_SwapBE32(1 << 0);
+ static const SK_OT_ULONG Latin1SupplementMask = SkTEndian_SwapBE32(1 << 1);
+ static const SK_OT_ULONG LatinExtendedAMask = SkTEndian_SwapBE32(1 << 2);
+ static const SK_OT_ULONG LatinExtendedBMask = SkTEndian_SwapBE32(1 << 3);
+ static const SK_OT_ULONG IPAExtensionsMask = SkTEndian_SwapBE32(1 << 4);
+ static const SK_OT_ULONG SpacingModifierLettersMask = SkTEndian_SwapBE32(1 << 5);
+ static const SK_OT_ULONG CombiningDiacriticalMarksMask = SkTEndian_SwapBE32(1 << 6);
+ static const SK_OT_ULONG GreekMask = SkTEndian_SwapBE32(1 << 7);
+ //Reserved
+ static const SK_OT_ULONG CyrillicMask = SkTEndian_SwapBE32(1 << 9);
+ static const SK_OT_ULONG ArmenianMask = SkTEndian_SwapBE32(1 << 10);
+ static const SK_OT_ULONG HebrewMask = SkTEndian_SwapBE32(1 << 11);
+ //Reserved
+ static const SK_OT_ULONG ArabicMask = SkTEndian_SwapBE32(1 << 13);
+ //Reserved
+ static const SK_OT_ULONG DevanagariMask = SkTEndian_SwapBE32(1 << 15);
+ static const SK_OT_ULONG BengaliMask = SkTEndian_SwapBE32(1 << 16);
+ static const SK_OT_ULONG GurmukhiMask = SkTEndian_SwapBE32(1 << 17);
+ static const SK_OT_ULONG GujaratiMask = SkTEndian_SwapBE32(1 << 18);
+ static const SK_OT_ULONG OriyaMask = SkTEndian_SwapBE32(1 << 19);
+ static const SK_OT_ULONG TamilMask = SkTEndian_SwapBE32(1 << 20);
+ static const SK_OT_ULONG TeluguMask = SkTEndian_SwapBE32(1 << 21);
+ static const SK_OT_ULONG KannadaMask = SkTEndian_SwapBE32(1 << 22);
+ static const SK_OT_ULONG MalayalamMask = SkTEndian_SwapBE32(1 << 23);
+ static const SK_OT_ULONG ThaiMask = SkTEndian_SwapBE32(1 << 24);
+ static const SK_OT_ULONG LaoMask = SkTEndian_SwapBE32(1 << 25);
+ static const SK_OT_ULONG GeorgianMask = SkTEndian_SwapBE32(1 << 26);
+ //Reserved
+ static const SK_OT_ULONG HangulJamoMask = SkTEndian_SwapBE32(1 << 28);
+ static const SK_OT_ULONG LatinExtendedAdditionalMask = SkTEndian_SwapBE32(1 << 29);
+ static const SK_OT_ULONG GreekExtendedMask = SkTEndian_SwapBE32(1 << 30);
+ static const SK_OT_ULONG GeneralPunctuationMask = SkTEndian_SwapBE32(1 << 31);
+ };
+ struct l1 {
+ static const SK_OT_ULONG SuperscriptsAndSubscriptsMask = SkTEndian_SwapBE32(1 << (32 - 32));
+ static const SK_OT_ULONG CurrencySymbolsMask = SkTEndian_SwapBE32(1 << (33 - 32));
+ static const SK_OT_ULONG CombiningDiacriticalMarksForSymbolsMask = SkTEndian_SwapBE32(1 << (34 - 32));
+ static const SK_OT_ULONG LetterlikeSymbolsMask = SkTEndian_SwapBE32(1 << (35 - 32));
+ static const SK_OT_ULONG NumberFormsMask = SkTEndian_SwapBE32(1 << (36 - 32));
+ static const SK_OT_ULONG ArrowsMask = SkTEndian_SwapBE32(1 << (37 - 32));
+ static const SK_OT_ULONG MathematicalOperatorsMask = SkTEndian_SwapBE32(1 << (38 - 32));
+ static const SK_OT_ULONG MiscellaneousTechnicalMask = SkTEndian_SwapBE32(1 << (39 - 32));
+ static const SK_OT_ULONG ControlPicturesMask = SkTEndian_SwapBE32(1 << (40 - 32));
+ static const SK_OT_ULONG OpticalCharacterRecognitionMask = SkTEndian_SwapBE32(1 << (41 - 32));
+ static const SK_OT_ULONG EnclosedAlphanumericsMask = SkTEndian_SwapBE32(1 << (42 - 32));
+ static const SK_OT_ULONG BoxDrawingMask = SkTEndian_SwapBE32(1 << (43 - 32));
+ static const SK_OT_ULONG BlockElementsMask = SkTEndian_SwapBE32(1 << (44 - 32));
+ static const SK_OT_ULONG GeometricShapesMask = SkTEndian_SwapBE32(1 << (45 - 32));
+ static const SK_OT_ULONG MiscellaneousSymbolsMask = SkTEndian_SwapBE32(1 << (46 - 32));
+ static const SK_OT_ULONG DingbatsMask = SkTEndian_SwapBE32(1 << (47 - 32));
+ static const SK_OT_ULONG CJKSymbolsAndPunctuationMask = SkTEndian_SwapBE32(1 << (48 - 32));
+ static const SK_OT_ULONG HiraganaMask = SkTEndian_SwapBE32(1 << (49 - 32));
+ static const SK_OT_ULONG KatakanaMask = SkTEndian_SwapBE32(1 << (50 - 32));
+ static const SK_OT_ULONG BopomofoMask = SkTEndian_SwapBE32(1 << (51 - 32));
+ static const SK_OT_ULONG HangulCompatibilityJamoMask = SkTEndian_SwapBE32(1 << (52 - 32));
+ static const SK_OT_ULONG CJKMiscellaneousMask = SkTEndian_SwapBE32(1 << (53 - 32));
+ static const SK_OT_ULONG EnclosedCJKLettersAndMonthsMask = SkTEndian_SwapBE32(1 << (54 - 32));
+ static const SK_OT_ULONG CJKCompatibilityMask = SkTEndian_SwapBE32(1 << (55 - 32));
+ static const SK_OT_ULONG HangulMask = SkTEndian_SwapBE32(1 << (56 - 32));
+ static const SK_OT_ULONG SurrogatesMask = SkTEndian_SwapBE32(1 << (57 - 32));
+ //Reserved
+ static const SK_OT_ULONG CJKUnifiedIdeographsMask = SkTEndian_SwapBE32(1 << (59 - 32));
+ static const SK_OT_ULONG PrivateUseAreaMask = SkTEndian_SwapBE32(1 << (60 - 32));
+ static const SK_OT_ULONG CJKCompatibilityIdeographsMask = SkTEndian_SwapBE32(1 << (61 - 32));
+ static const SK_OT_ULONG AlphabeticPresentationFormsMask = SkTEndian_SwapBE32(1 << (62 - 32));
+ static const SK_OT_ULONG ArabicPresentationFormsAMask = SkTEndian_SwapBE32(1 << (63 - 32));
+ };
+ struct l2 {
+ static const SK_OT_ULONG CombiningHalfMarksMask = SkTEndian_SwapBE32(1 << (64 - 64));
+ static const SK_OT_ULONG CJKCompatibilityFormsMask = SkTEndian_SwapBE32(1 << (65 - 64));
+ static const SK_OT_ULONG SmallFormVariantsMask = SkTEndian_SwapBE32(1 << (66 - 64));
+ static const SK_OT_ULONG ArabicPresentationFormsBMask = SkTEndian_SwapBE32(1 << (67 - 64));
+ static const SK_OT_ULONG HalfwidthAndFullwidthFormsMask = SkTEndian_SwapBE32(1 << (68 - 64));
+ static const SK_OT_ULONG SpecialsMask = SkTEndian_SwapBE32(1 << (69 - 64));
+ static const SK_OT_ULONG TibetanMask = SkTEndian_SwapBE32(1 << (70 - 64));
+ static const SK_OT_ULONG SyriacMask = SkTEndian_SwapBE32(1 << (71 - 64));
+ static const SK_OT_ULONG ThaanaMask = SkTEndian_SwapBE32(1 << (72 - 64));
+ static const SK_OT_ULONG SinhalaMask = SkTEndian_SwapBE32(1 << (73 - 64));
+ static const SK_OT_ULONG MyanmarMask = SkTEndian_SwapBE32(1 << (74 - 64));
+ static const SK_OT_ULONG EthiopicMask = SkTEndian_SwapBE32(1 << (75 - 64));
+ static const SK_OT_ULONG CherokeeMask = SkTEndian_SwapBE32(1 << (76 - 64));
+ static const SK_OT_ULONG UnifiedCanadianSyllabicsMask = SkTEndian_SwapBE32(1 << (77 - 64));
+ static const SK_OT_ULONG OghamMask = SkTEndian_SwapBE32(1 << (78 - 64));
+ static const SK_OT_ULONG RunicMask = SkTEndian_SwapBE32(1 << (79 - 64));
+ static const SK_OT_ULONG KhmerMask = SkTEndian_SwapBE32(1 << (80 - 64));
+ static const SK_OT_ULONG MongolianMask = SkTEndian_SwapBE32(1 << (81 - 64));
+ static const SK_OT_ULONG BrailleMask = SkTEndian_SwapBE32(1 << (82 - 64));
+ static const SK_OT_ULONG YiMask = SkTEndian_SwapBE32(1 << (83 - 64));
+ };
+ SK_OT_ULONG value[4];
+ } raw;
+ } ulUnicodeRange;
+ SK_OT_CHAR achVendID[4];
+ union Selection {
+ struct Field {
+ //8-15
+ SK_OT_BYTE_BITFIELD(
+ Reserved08,
+ Reserved09,
+ Reserved10,
+ Reserved11,
+ Reserved12,
+ Reserved13,
+ Reserved14,
+ Reserved15)
+ //0-7
+ SK_OT_BYTE_BITFIELD(
+ Italic,
+ Underscore,
+ Negative,
+ Outlined,
+ Strikeout,
+ Bold,
+ Regular,
+ Reserved07)
+ } field;
+ struct Raw {
+ static const SK_OT_USHORT ItalicMask = SkTEndian_SwapBE16(1 << 0);
+ static const SK_OT_USHORT UnderscoreMask = SkTEndian_SwapBE16(1 << 1);
+ static const SK_OT_USHORT NegativeMask = SkTEndian_SwapBE16(1 << 2);
+ static const SK_OT_USHORT OutlinedMask = SkTEndian_SwapBE16(1 << 3);
+ static const SK_OT_USHORT StrikeoutMask = SkTEndian_SwapBE16(1 << 4);
+ static const SK_OT_USHORT BoldMask = SkTEndian_SwapBE16(1 << 5);
+ static const SK_OT_USHORT RegularMask = SkTEndian_SwapBE16(1 << 6);
+ SK_OT_USHORT value;
+ } raw;
+ } fsSelection;
+ SK_OT_USHORT usFirstCharIndex;
+ SK_OT_USHORT usLastCharIndex;
+ //version0
+ SK_OT_SHORT sTypoAscender;
+ SK_OT_SHORT sTypoDescender;
+ SK_OT_SHORT sTypoLineGap;
+ SK_OT_USHORT usWinAscent;
+ SK_OT_USHORT usWinDescent;
+ //version1
+ union CodePageRange {
+ struct Field {
+ //l0 24-31
+ SK_OT_BYTE_BITFIELD(
+ Reserved24,
+ Reserved25,
+ Reserved26,
+ Reserved27,
+ Reserved28,
+ MacintoshCharacterSet,
+ OEMCharacterSet,
+ SymbolCharacterSet)
+ //l0 16-23
+ SK_OT_BYTE_BITFIELD(
+ Thai_874,
+ JISJapan_932,
+ ChineseSimplified_936,
+ KoreanWansung_949,
+ ChineseTraditional_950,
+ KoreanJohab_1361,
+ Reserved22,
+ Reserved23)
+ //l0 8-15
+ SK_OT_BYTE_BITFIELD(
+ Vietnamese,
+ Reserved09,
+ Reserved10,
+ Reserved11,
+ Reserved12,
+ Reserved13,
+ Reserved14,
+ Reserved15)
+ //l0 0-7
+ SK_OT_BYTE_BITFIELD(
+ Latin1_1252,
+ Latin2EasternEurope_1250,
+ Cyrillic_1251,
+ Greek_1253,
+ Turkish_1254,
+ Hebrew_1255,
+ Arabic_1256,
+ WindowsBaltic_1257)
+
+ //l1 24-31
+ SK_OT_BYTE_BITFIELD(
+ IBMTurkish_857,
+ IBMCyrillic_855,
+ Latin2_852,
+ MSDOSBaltic_775,
+ Greek_737,
+ Arabic_708,
+ WELatin1_850,
+ US_437)
+ //l1 16-23
+ SK_OT_BYTE_BITFIELD(
+ IBMGreek_869,
+ MSDOSRussian_866,
+ MSDOSNordic_865,
+ Arabic_864,
+ MSDOSCanadianFrench_863,
+ Hebrew_862,
+ MSDOSIcelandic_861,
+ MSDOSPortuguese_860)
+ //l1 8-15
+ SK_OT_BYTE_BITFIELD(
+ Reserved40,
+ Reserved41,
+ Reserved42,
+ Reserved43,
+ Reserved44,
+ Reserved45,
+ Reserved46,
+ Reserved47)
+ //l1 0-7
+ SK_OT_BYTE_BITFIELD(
+ Reserved32,
+ Reserved33,
+ Reserved34,
+ Reserved35,
+ Reserved36,
+ Reserved37,
+ Reserved38,
+ Reserved39)
+ } field;
+ struct Raw {
+ struct l0 {
+ static const SK_OT_ULONG Latin1_1252Mask = SkTEndian_SwapBE32(1 << 0);
+ static const SK_OT_ULONG Latin2EasternEurope_1250Mask = SkTEndian_SwapBE32(1 << 1);
+ static const SK_OT_ULONG Cyrillic_1251Mask = SkTEndian_SwapBE32(1 << 2);
+ static const SK_OT_ULONG Greek_1253Mask = SkTEndian_SwapBE32(1 << 3);
+ static const SK_OT_ULONG Turkish_1254Mask = SkTEndian_SwapBE32(1 << 4);
+ static const SK_OT_ULONG Hebrew_1255Mask = SkTEndian_SwapBE32(1 << 5);
+ static const SK_OT_ULONG Arabic_1256Mask = SkTEndian_SwapBE32(1 << 6);
+ static const SK_OT_ULONG WindowsBaltic_1257Mask = SkTEndian_SwapBE32(1 << 7);
+ static const SK_OT_ULONG Vietnamese_1258Mask = SkTEndian_SwapBE32(1 << 8);
+ static const SK_OT_ULONG Thai_874Mask = SkTEndian_SwapBE32(1 << 16);
+ static const SK_OT_ULONG JISJapan_932Mask = SkTEndian_SwapBE32(1 << 17);
+ static const SK_OT_ULONG ChineseSimplified_936Mask = SkTEndian_SwapBE32(1 << 18);
+ static const SK_OT_ULONG KoreanWansung_949Mask = SkTEndian_SwapBE32(1 << 19);
+ static const SK_OT_ULONG ChineseTraditional_950Mask = SkTEndian_SwapBE32(1 << 20);
+ static const SK_OT_ULONG KoreanJohab_1361Mask = SkTEndian_SwapBE32(1 << 21);
+ static const SK_OT_ULONG MacintoshCharacterSetMask = SkTEndian_SwapBE32(1 << 29);
+ static const SK_OT_ULONG OEMCharacterSetMask = SkTEndian_SwapBE32(1 << 30);
+ static const SK_OT_ULONG SymbolCharacterSetMask = SkTEndian_SwapBE32(1 << 31);
+ };
+ struct l1 {
+ static const SK_OT_ULONG IBMGreek_869Mask = SkTEndian_SwapBE32(1 << (48 - 32));
+ static const SK_OT_ULONG MSDOSRussian_866Mask = SkTEndian_SwapBE32(1 << (49 - 32));
+ static const SK_OT_ULONG MSDOSNordic_865Mask = SkTEndian_SwapBE32(1 << (50 - 32));
+ static const SK_OT_ULONG Arabic_864Mask = SkTEndian_SwapBE32(1 << (51 - 32));
+ static const SK_OT_ULONG MSDOSCanadianFrench_863Mask = SkTEndian_SwapBE32(1 << (52 - 32));
+ static const SK_OT_ULONG Hebrew_862Mask = SkTEndian_SwapBE32(1 << (53 - 32));
+ static const SK_OT_ULONG MSDOSIcelandic_861Mask = SkTEndian_SwapBE32(1 << (54 - 32));
+ static const SK_OT_ULONG MSDOSPortuguese_860Mask = SkTEndian_SwapBE32(1 << (55 - 32));
+ static const SK_OT_ULONG IBMTurkish_857Mask = SkTEndian_SwapBE32(1 << (56 - 32));
+ static const SK_OT_ULONG IBMCyrillic_855Mask = SkTEndian_SwapBE32(1 << (57 - 32));
+ static const SK_OT_ULONG Latin2_852Mask = SkTEndian_SwapBE32(1 << (58 - 32));
+ static const SK_OT_ULONG MSDOSBaltic_775Mask = SkTEndian_SwapBE32(1 << (59 - 32));
+ static const SK_OT_ULONG Greek_737Mask = SkTEndian_SwapBE32(1 << (60 - 32));
+ static const SK_OT_ULONG Arabic_708Mask = SkTEndian_SwapBE32(1 << (61 - 32));
+ static const SK_OT_ULONG WELatin1_850Mask = SkTEndian_SwapBE32(1 << (62 - 32));
+ static const SK_OT_ULONG US_437Mask = SkTEndian_SwapBE32(1 << (63 - 32));
+ };
+ SK_OT_ULONG value[2];
+ } raw;
+ } ulCodePageRange;
+ //version2
+ SK_OT_SHORT sxHeight;
+ SK_OT_SHORT sCapHeight;
+ SK_OT_USHORT usDefaultChar;
+ SK_OT_USHORT usBreakChar;
+ SK_OT_USHORT usMaxContext;
+};
+
+#pragma pack(pop)
+
+
+SK_COMPILE_ASSERT(sizeof(SkOTTableOS2_V2) == 96, sizeof_SkOTTableOS2_V2_not_96);
+
+#endif
diff --git a/src/sfnt/SkOTTable_OS_2_V3.h b/src/sfnt/SkOTTable_OS_2_V3.h
new file mode 100644
index 0000000000..637eb378ad
--- /dev/null
+++ b/src/sfnt/SkOTTable_OS_2_V3.h
@@ -0,0 +1,550 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkOTTable_OS_2_V3_DEFINED
+#define SkOTTable_OS_2_V3_DEFINED
+
+#include "SkEndian.h"
+#include "SkIBMFamilyClass.h"
+#include "SkOTTableTypes.h"
+#include "SkPanose.h"
+#include "SkTypedEnum.h"
+
+#pragma pack(push, 1)
+
+struct SkOTTableOS2_V3 {
+ SK_OT_USHORT version;
+ static const SK_OT_USHORT VERSION = SkTEndian_SwapBE16(3);
+
+ SK_OT_SHORT xAvgCharWidth;
+ struct WeightClass {
+ SK_TYPED_ENUM(Value, SK_OT_USHORT,
+ ((Thin, SkTEndian_SwapBE16(100)))
+ ((ExtraLight, SkTEndian_SwapBE16(200)))
+ ((Light, SkTEndian_SwapBE16(300)))
+ ((Normal, SkTEndian_SwapBE16(400)))
+ ((Medium, SkTEndian_SwapBE16(500)))
+ ((SemiBold, SkTEndian_SwapBE16(600)))
+ ((Bold, SkTEndian_SwapBE16(700)))
+ ((ExtraBold, SkTEndian_SwapBE16(800)))
+ ((Black, SkTEndian_SwapBE16(900)))
+ SK_SEQ_END,
+ SK_SEQ_END)
+ SK_OT_USHORT value;
+ } usWeightClass;
+ struct WidthClass {
+ SK_TYPED_ENUM(Value, SK_OT_USHORT,
+ ((UltraCondensed, SkTEndian_SwapBE16(1)))
+ ((ExtraCondensed, SkTEndian_SwapBE16(2)))
+ ((Condensed, SkTEndian_SwapBE16(3)))
+ ((SemiCondensed, SkTEndian_SwapBE16(4)))
+ ((Medium, SkTEndian_SwapBE16(5)))
+ ((SemiExpanded, SkTEndian_SwapBE16(6)))
+ ((Expanded, SkTEndian_SwapBE16(7)))
+ ((ExtraExpanded, SkTEndian_SwapBE16(8)))
+ ((UltraExpanded, SkTEndian_SwapBE16(9)))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } usWidthClass;
+ union Type {
+ struct Field {
+ //8-15
+ SK_OT_BYTE_BITFIELD(
+ NoSubsetting,
+ Bitmap,
+ Reserved10,
+ Reserved11,
+ Reserved12,
+ Reserved13,
+ Reserved14,
+ Reserved15)
+ //0-7
+ SK_OT_BYTE_BITFIELD(
+ Reserved00,
+ Restricted,
+ PreviewPrint,
+ Editable,
+ Reserved04,
+ Reserved05,
+ Reserved06,
+ Reserved07)
+ } field;
+ struct Raw {
+ static const SK_OT_USHORT Installable = SkTEndian_SwapBE16(0);
+ static const SK_OT_USHORT RestrictedMask = SkTEndian_SwapBE16(1 << 1);
+ static const SK_OT_USHORT PreviewPrintMask = SkTEndian_SwapBE16(1 << 2);
+ static const SK_OT_USHORT EditableMask = SkTEndian_SwapBE16(1 << 3);
+ static const SK_OT_USHORT NoSubsettingMask = SkTEndian_SwapBE16(1 << 8);
+ static const SK_OT_USHORT BitmapMask = SkTEndian_SwapBE16(1 << 9);
+ SK_OT_USHORT value;
+ } raw;
+ } fsType;
+ SK_OT_SHORT ySubscriptXSize;
+ SK_OT_SHORT ySubscriptYSize;
+ SK_OT_SHORT ySubscriptXOffset;
+ SK_OT_SHORT ySubscriptYOffset;
+ SK_OT_SHORT ySuperscriptXSize;
+ SK_OT_SHORT ySuperscriptYSize;
+ SK_OT_SHORT ySuperscriptXOffset;
+ SK_OT_SHORT ySuperscriptYOffset;
+ SK_OT_SHORT yStrikeoutSize;
+ SK_OT_SHORT yStrikeoutPosition;
+ SkIBMFamilyClass sFamilyClass;
+ SkPanose panose;
+ union UnicodeRange {
+ struct Field {
+ //l0 24-31
+ SK_OT_BYTE_BITFIELD(
+ Thai,
+ Lao,
+ Georgian,
+ Reserved027,
+ HangulJamo,
+ LatinExtendedAdditional,
+ GreekExtended,
+ GeneralPunctuation)
+ //l0 16-23
+ SK_OT_BYTE_BITFIELD(
+ Bengali,
+ Gurmukhi,
+ Gujarati,
+ Oriya,
+ Tamil,
+ Telugu,
+ Kannada,
+ Malayalam)
+ //l0 8-15
+ SK_OT_BYTE_BITFIELD(
+ Reserved008,
+ Cyrillic,
+ Armenian,
+ Hebrew,
+ Reserved012,
+ Arabic,
+ Reserved014,
+ Devanagari)
+ //l0 0-7
+ SK_OT_BYTE_BITFIELD(
+ BasicLatin,
+ Latin1Supplement,
+ LatinExtendedA,
+ LatinExtendedB,
+ IPAExtensions,
+ SpacingModifierLetters,
+ CombiningDiacriticalMarks,
+ GreekAndCoptic)
+
+ //l1 24-31
+ SK_OT_BYTE_BITFIELD(
+ Hangul,
+ NonPlane0,
+ Reserved058,
+ CJKUnifiedIdeographs,
+ PrivateUseArea,
+ CJKCompatibilityIdeographs,
+ AlphabeticPresentationForms,
+ ArabicPresentationFormsA)
+ //l1 16-23
+ SK_OT_BYTE_BITFIELD(
+ CJKSymbolsAndPunctuation,
+ Hiragana,
+ Katakana,
+ Bopomofo,
+ HangulCompatibilityJamo,
+ Reserved053,
+ EnclosedCJKLettersAndMonths,
+ CJKCompatibility)
+ //l1 8-15
+ SK_OT_BYTE_BITFIELD(
+ ControlPictures,
+ OpticalCharacterRecognition,
+ EnclosedAlphanumerics,
+ BoxDrawing,
+ BlockElements,
+ GeometricShapes,
+ MiscellaneousSymbols,
+ Dingbats)
+ //l1 0-7
+ SK_OT_BYTE_BITFIELD(
+ SuperscriptsAndSubscripts,
+ CurrencySymbols,
+ CombiningDiacriticalMarksForSymbols,
+ LetterlikeSymbols,
+ NumberForms,
+ Arrows,
+ MathematicalOperators,
+ MiscellaneousTechnical)
+
+ //l2 24-31
+ SK_OT_BYTE_BITFIELD(
+ MusicalSymbols,
+ MathematicalAlphanumericSymbols,
+ PrivateUse,
+ VariationSelectors,
+ Tags,
+ Reserved093,
+ Reserved094,
+ Reserved095)
+ //l2 16-23
+ SK_OT_BYTE_BITFIELD(
+ Khmer,
+ Mongolian,
+ Braille,
+ Yi,
+ Tagalog_Hanunoo_Buhid_Tagbanwa,
+ OldItalic,
+ Gothic,
+ Deseret)
+ //l2 8-15
+ SK_OT_BYTE_BITFIELD(
+ Thaana,
+ Sinhala,
+ Myanmar,
+ Ethiopic,
+ Cherokee,
+ UnifiedCanadianSyllabics,
+ Ogham,
+ Runic)
+ //l2 0-7
+ SK_OT_BYTE_BITFIELD(
+ CombiningHalfMarks,
+ CJKCompatibilityForms,
+ SmallFormVariants,
+ ArabicPresentationFormsB,
+ HalfwidthAndFullwidthForms,
+ Specials,
+ Tibetan,
+ Syriac)
+
+ //l3 24-31
+ SK_OT_BYTE_BITFIELD(
+ Reserved120,
+ Reserved121,
+ Reserved122,
+ Reserved123,
+ Reserved124,
+ Reserved125,
+ Reserved126,
+ Reserved127)
+ //l3 16-23
+ SK_OT_BYTE_BITFIELD(
+ Reserved112,
+ Reserved113,
+ Reserved114,
+ Reserved115,
+ Reserved116,
+ Reserved117,
+ Reserved118,
+ Reserved119)
+ //l3 8-15
+ SK_OT_BYTE_BITFIELD(
+ Reserved104,
+ Reserved105,
+ Reserved106,
+ Reserved107,
+ Reserved108,
+ Reserved109,
+ Reserved110,
+ Reserved111)
+ //l3 0-7
+ SK_OT_BYTE_BITFIELD(
+ Reserved096,
+ Reserved097,
+ Reserved098,
+ Reserved099,
+ Reserved100,
+ Reserved101,
+ Reserved102,
+ Reserved103)
+ } field;
+ struct Raw {
+ struct l0 {
+ static const SK_OT_ULONG BasicLatinMask = SkTEndian_SwapBE32(1 << 0);
+ static const SK_OT_ULONG Latin1SupplementMask = SkTEndian_SwapBE32(1 << 1);
+ static const SK_OT_ULONG LatinExtendedAMask = SkTEndian_SwapBE32(1 << 2);
+ static const SK_OT_ULONG LatinExtendedBMask = SkTEndian_SwapBE32(1 << 3);
+ static const SK_OT_ULONG IPAExtensionsMask = SkTEndian_SwapBE32(1 << 4);
+ static const SK_OT_ULONG SpacingModifierLettersMask = SkTEndian_SwapBE32(1 << 5);
+ static const SK_OT_ULONG CombiningDiacriticalMarksMask = SkTEndian_SwapBE32(1 << 6);
+ static const SK_OT_ULONG GreekAndCopticMask = SkTEndian_SwapBE32(1 << 7);
+ //Reserved
+ static const SK_OT_ULONG CyrillicMask = SkTEndian_SwapBE32(1 << 9);
+ static const SK_OT_ULONG ArmenianMask = SkTEndian_SwapBE32(1 << 10);
+ static const SK_OT_ULONG HebrewMask = SkTEndian_SwapBE32(1 << 11);
+ //Reserved
+ static const SK_OT_ULONG ArabicMask = SkTEndian_SwapBE32(1 << 13);
+ //Reserved
+ static const SK_OT_ULONG DevanagariMask = SkTEndian_SwapBE32(1 << 15);
+ static const SK_OT_ULONG BengaliMask = SkTEndian_SwapBE32(1 << 16);
+ static const SK_OT_ULONG GurmukhiMask = SkTEndian_SwapBE32(1 << 17);
+ static const SK_OT_ULONG GujaratiMask = SkTEndian_SwapBE32(1 << 18);
+ static const SK_OT_ULONG OriyaMask = SkTEndian_SwapBE32(1 << 19);
+ static const SK_OT_ULONG TamilMask = SkTEndian_SwapBE32(1 << 20);
+ static const SK_OT_ULONG TeluguMask = SkTEndian_SwapBE32(1 << 21);
+ static const SK_OT_ULONG KannadaMask = SkTEndian_SwapBE32(1 << 22);
+ static const SK_OT_ULONG MalayalamMask = SkTEndian_SwapBE32(1 << 23);
+ static const SK_OT_ULONG ThaiMask = SkTEndian_SwapBE32(1 << 24);
+ static const SK_OT_ULONG LaoMask = SkTEndian_SwapBE32(1 << 25);
+ static const SK_OT_ULONG GeorgianMask = SkTEndian_SwapBE32(1 << 26);
+ //Reserved
+ static const SK_OT_ULONG HangulJamoMask = SkTEndian_SwapBE32(1 << 28);
+ static const SK_OT_ULONG LatinExtendedAdditionalMask = SkTEndian_SwapBE32(1 << 29);
+ static const SK_OT_ULONG GreekExtendedMask = SkTEndian_SwapBE32(1 << 30);
+ static const SK_OT_ULONG GeneralPunctuationMask = SkTEndian_SwapBE32(1 << 31);
+ };
+ struct l1 {
+ static const SK_OT_ULONG SuperscriptsAndSubscriptsMask = SkTEndian_SwapBE32(1 << (32 - 32));
+ static const SK_OT_ULONG CurrencySymbolsMask = SkTEndian_SwapBE32(1 << (33 - 32));
+ static const SK_OT_ULONG CombiningDiacriticalMarksForSymbolsMask = SkTEndian_SwapBE32(1 << (34 - 32));
+ static const SK_OT_ULONG LetterlikeSymbolsMask = SkTEndian_SwapBE32(1 << (35 - 32));
+ static const SK_OT_ULONG NumberFormsMask = SkTEndian_SwapBE32(1 << (36 - 32));
+ static const SK_OT_ULONG ArrowsMask = SkTEndian_SwapBE32(1 << (37 - 32));
+ static const SK_OT_ULONG MathematicalOperatorsMask = SkTEndian_SwapBE32(1 << (38 - 32));
+ static const SK_OT_ULONG MiscellaneousTechnicalMask = SkTEndian_SwapBE32(1 << (39 - 32));
+ static const SK_OT_ULONG ControlPicturesMask = SkTEndian_SwapBE32(1 << (40 - 32));
+ static const SK_OT_ULONG OpticalCharacterRecognitionMask = SkTEndian_SwapBE32(1 << (41 - 32));
+ static const SK_OT_ULONG EnclosedAlphanumericsMask = SkTEndian_SwapBE32(1 << (42 - 32));
+ static const SK_OT_ULONG BoxDrawingMask = SkTEndian_SwapBE32(1 << (43 - 32));
+ static const SK_OT_ULONG BlockElementsMask = SkTEndian_SwapBE32(1 << (44 - 32));
+ static const SK_OT_ULONG GeometricShapesMask = SkTEndian_SwapBE32(1 << (45 - 32));
+ static const SK_OT_ULONG MiscellaneousSymbolsMask = SkTEndian_SwapBE32(1 << (46 - 32));
+ static const SK_OT_ULONG DingbatsMask = SkTEndian_SwapBE32(1 << (47 - 32));
+ static const SK_OT_ULONG CJKSymbolsAndPunctuationMask = SkTEndian_SwapBE32(1 << (48 - 32));
+ static const SK_OT_ULONG HiraganaMask = SkTEndian_SwapBE32(1 << (49 - 32));
+ static const SK_OT_ULONG KatakanaMask = SkTEndian_SwapBE32(1 << (50 - 32));
+ static const SK_OT_ULONG BopomofoMask = SkTEndian_SwapBE32(1 << (51 - 32));
+ static const SK_OT_ULONG HangulCompatibilityJamoMask = SkTEndian_SwapBE32(1 << (52 - 32));
+ //Reserved
+ static const SK_OT_ULONG EnclosedCJKLettersAndMonthsMask = SkTEndian_SwapBE32(1 << (54 - 32));
+ static const SK_OT_ULONG CJKCompatibilityMask = SkTEndian_SwapBE32(1 << (55 - 32));
+ static const SK_OT_ULONG HangulMask = SkTEndian_SwapBE32(1 << (56 - 32));
+ static const SK_OT_ULONG NonPlane0Mask = SkTEndian_SwapBE32(1 << (57 - 32));
+ //Reserved
+ static const SK_OT_ULONG CJKUnifiedIdeographsMask = SkTEndian_SwapBE32(1 << (59 - 32));
+ static const SK_OT_ULONG PrivateUseAreaMask = SkTEndian_SwapBE32(1 << (60 - 32));
+ static const SK_OT_ULONG CJKCompatibilityIdeographsMask = SkTEndian_SwapBE32(1 << (61 - 32));
+ static const SK_OT_ULONG AlphabeticPresentationFormsMask = SkTEndian_SwapBE32(1 << (62 - 32));
+ static const SK_OT_ULONG ArabicPresentationFormsAMask = SkTEndian_SwapBE32(1 << (63 - 32));
+ };
+ struct l2 {
+ static const SK_OT_ULONG CombiningHalfMarksMask = SkTEndian_SwapBE32(1 << (64 - 64));
+ static const SK_OT_ULONG CJKCompatibilityFormsMask = SkTEndian_SwapBE32(1 << (65 - 64));
+ static const SK_OT_ULONG SmallFormVariantsMask = SkTEndian_SwapBE32(1 << (66 - 64));
+ static const SK_OT_ULONG ArabicPresentationFormsBMask = SkTEndian_SwapBE32(1 << (67 - 64));
+ static const SK_OT_ULONG HalfwidthAndFullwidthFormsMask = SkTEndian_SwapBE32(1 << (68 - 64));
+ static const SK_OT_ULONG SpecialsMask = SkTEndian_SwapBE32(1 << (69 - 64));
+ static const SK_OT_ULONG TibetanMask = SkTEndian_SwapBE32(1 << (70 - 64));
+ static const SK_OT_ULONG SyriacMask = SkTEndian_SwapBE32(1 << (71 - 64));
+ static const SK_OT_ULONG ThaanaMask = SkTEndian_SwapBE32(1 << (72 - 64));
+ static const SK_OT_ULONG SinhalaMask = SkTEndian_SwapBE32(1 << (73 - 64));
+ static const SK_OT_ULONG MyanmarMask = SkTEndian_SwapBE32(1 << (74 - 64));
+ static const SK_OT_ULONG EthiopicMask = SkTEndian_SwapBE32(1 << (75 - 64));
+ static const SK_OT_ULONG CherokeeMask = SkTEndian_SwapBE32(1 << (76 - 64));
+ static const SK_OT_ULONG UnifiedCanadianSyllabicsMask = SkTEndian_SwapBE32(1 << (77 - 64));
+ static const SK_OT_ULONG OghamMask = SkTEndian_SwapBE32(1 << (78 - 64));
+ static const SK_OT_ULONG RunicMask = SkTEndian_SwapBE32(1 << (79 - 64));
+ static const SK_OT_ULONG KhmerMask = SkTEndian_SwapBE32(1 << (80 - 64));
+ static const SK_OT_ULONG MongolianMask = SkTEndian_SwapBE32(1 << (81 - 64));
+ static const SK_OT_ULONG BrailleMask = SkTEndian_SwapBE32(1 << (82 - 64));
+ static const SK_OT_ULONG YiMask = SkTEndian_SwapBE32(1 << (83 - 64));
+ static const SK_OT_ULONG Tagalog_Hanunoo_Buhid_TagbanwaMask = SkTEndian_SwapBE32(1 << (84 - 64));
+ static const SK_OT_ULONG OldItalicMask = SkTEndian_SwapBE32(1 << (85 - 64));
+ static const SK_OT_ULONG GothicMask = SkTEndian_SwapBE32(1 << (86 - 64));
+ static const SK_OT_ULONG DeseretMask = SkTEndian_SwapBE32(1 << (87 - 64));
+ static const SK_OT_ULONG MusicalSymbolsMask = SkTEndian_SwapBE32(1 << (88 - 64));
+ static const SK_OT_ULONG MathematicalAlphanumericSymbolsMask = SkTEndian_SwapBE32(1 << (89 - 64));
+ static const SK_OT_ULONG PrivateUseMask = SkTEndian_SwapBE32(1 << (90 - 64));
+ static const SK_OT_ULONG VariationSelectorsMask = SkTEndian_SwapBE32(1 << (91 - 64));
+ static const SK_OT_ULONG TagsMask = SkTEndian_SwapBE32(1 << (92 - 64));
+ };
+ SK_OT_ULONG value[4];
+ } raw;
+ } ulUnicodeRange;
+ SK_OT_CHAR achVendID[4];
+ union Selection {
+ struct Field {
+ //8-15
+ SK_OT_BYTE_BITFIELD(
+ Reserved08,
+ Reserved09,
+ Reserved10,
+ Reserved11,
+ Reserved12,
+ Reserved13,
+ Reserved14,
+ Reserved15)
+ //0-7
+ SK_OT_BYTE_BITFIELD(
+ Italic,
+ Underscore,
+ Negative,
+ Outlined,
+ Strikeout,
+ Bold,
+ Regular,
+ Reserved07)
+ } field;
+ struct Raw {
+ static const SK_OT_USHORT ItalicMask = SkTEndian_SwapBE16(1 << 0);
+ static const SK_OT_USHORT UnderscoreMask = SkTEndian_SwapBE16(1 << 1);
+ static const SK_OT_USHORT NegativeMask = SkTEndian_SwapBE16(1 << 2);
+ static const SK_OT_USHORT OutlinedMask = SkTEndian_SwapBE16(1 << 3);
+ static const SK_OT_USHORT StrikeoutMask = SkTEndian_SwapBE16(1 << 4);
+ static const SK_OT_USHORT BoldMask = SkTEndian_SwapBE16(1 << 5);
+ static const SK_OT_USHORT RegularMask = SkTEndian_SwapBE16(1 << 6);
+ SK_OT_USHORT value;
+ } raw;
+ } fsSelection;
+ SK_OT_USHORT usFirstCharIndex;
+ SK_OT_USHORT usLastCharIndex;
+ //version0
+ SK_OT_SHORT sTypoAscender;
+ SK_OT_SHORT sTypoDescender;
+ SK_OT_SHORT sTypoLineGap;
+ SK_OT_USHORT usWinAscent;
+ SK_OT_USHORT usWinDescent;
+ //version1
+ union CodePageRange {
+ struct Field {
+ //l0 24-31
+ SK_OT_BYTE_BITFIELD(
+ Reserved24,
+ Reserved25,
+ Reserved26,
+ Reserved27,
+ Reserved28,
+ MacintoshCharacterSet,
+ OEMCharacterSet,
+ SymbolCharacterSet)
+ //l0 16-23
+ SK_OT_BYTE_BITFIELD(
+ Thai_874,
+ JISJapan_932,
+ ChineseSimplified_936,
+ KoreanWansung_949,
+ ChineseTraditional_950,
+ KoreanJohab_1361,
+ Reserved22,
+ Reserved23)
+ //l0 8-15
+ SK_OT_BYTE_BITFIELD(
+ Vietnamese,
+ Reserved09,
+ Reserved10,
+ Reserved11,
+ Reserved12,
+ Reserved13,
+ Reserved14,
+ Reserved15)
+ //l0 0-7
+ SK_OT_BYTE_BITFIELD(
+ Latin1_1252,
+ Latin2EasternEurope_1250,
+ Cyrillic_1251,
+ Greek_1253,
+ Turkish_1254,
+ Hebrew_1255,
+ Arabic_1256,
+ WindowsBaltic_1257)
+
+ //l1 24-31
+ SK_OT_BYTE_BITFIELD(
+ IBMTurkish_857,
+ IBMCyrillic_855,
+ Latin2_852,
+ MSDOSBaltic_775,
+ Greek_737,
+ Arabic_708,
+ WELatin1_850,
+ US_437)
+ //l1 16-23
+ SK_OT_BYTE_BITFIELD(
+ IBMGreek_869,
+ MSDOSRussian_866,
+ MSDOSNordic_865,
+ Arabic_864,
+ MSDOSCanadianFrench_863,
+ Hebrew_862,
+ MSDOSIcelandic_861,
+ MSDOSPortuguese_860)
+ //l1 8-15
+ SK_OT_BYTE_BITFIELD(
+ Reserved40,
+ Reserved41,
+ Reserved42,
+ Reserved43,
+ Reserved44,
+ Reserved45,
+ Reserved46,
+ Reserved47)
+ //l1 0-7
+ SK_OT_BYTE_BITFIELD(
+ Reserved32,
+ Reserved33,
+ Reserved34,
+ Reserved35,
+ Reserved36,
+ Reserved37,
+ Reserved38,
+ Reserved39)
+ } field;
+ struct Raw {
+ struct l0 {
+ static const SK_OT_ULONG Latin1_1252Mask = SkTEndian_SwapBE32(1 << 0);
+ static const SK_OT_ULONG Latin2EasternEurope_1250Mask = SkTEndian_SwapBE32(1 << 1);
+ static const SK_OT_ULONG Cyrillic_1251Mask = SkTEndian_SwapBE32(1 << 2);
+ static const SK_OT_ULONG Greek_1253Mask = SkTEndian_SwapBE32(1 << 3);
+ static const SK_OT_ULONG Turkish_1254Mask = SkTEndian_SwapBE32(1 << 4);
+ static const SK_OT_ULONG Hebrew_1255Mask = SkTEndian_SwapBE32(1 << 5);
+ static const SK_OT_ULONG Arabic_1256Mask = SkTEndian_SwapBE32(1 << 6);
+ static const SK_OT_ULONG WindowsBaltic_1257Mask = SkTEndian_SwapBE32(1 << 7);
+ static const SK_OT_ULONG Vietnamese_1258Mask = SkTEndian_SwapBE32(1 << 8);
+ static const SK_OT_ULONG Thai_874Mask = SkTEndian_SwapBE32(1 << 16);
+ static const SK_OT_ULONG JISJapan_932Mask = SkTEndian_SwapBE32(1 << 17);
+ static const SK_OT_ULONG ChineseSimplified_936Mask = SkTEndian_SwapBE32(1 << 18);
+ static const SK_OT_ULONG KoreanWansung_949Mask = SkTEndian_SwapBE32(1 << 19);
+ static const SK_OT_ULONG ChineseTraditional_950Mask = SkTEndian_SwapBE32(1 << 20);
+ static const SK_OT_ULONG KoreanJohab_1361Mask = SkTEndian_SwapBE32(1 << 21);
+ static const SK_OT_ULONG MacintoshCharacterSetMask = SkTEndian_SwapBE32(1 << 29);
+ static const SK_OT_ULONG OEMCharacterSetMask = SkTEndian_SwapBE32(1 << 30);
+ static const SK_OT_ULONG SymbolCharacterSetMask = SkTEndian_SwapBE32(1 << 31);
+ };
+ struct l1 {
+ static const SK_OT_ULONG IBMGreek_869Mask = SkTEndian_SwapBE32(1 << (48 - 32));
+ static const SK_OT_ULONG MSDOSRussian_866Mask = SkTEndian_SwapBE32(1 << (49 - 32));
+ static const SK_OT_ULONG MSDOSNordic_865Mask = SkTEndian_SwapBE32(1 << (50 - 32));
+ static const SK_OT_ULONG Arabic_864Mask = SkTEndian_SwapBE32(1 << (51 - 32));
+ static const SK_OT_ULONG MSDOSCanadianFrench_863Mask = SkTEndian_SwapBE32(1 << (52 - 32));
+ static const SK_OT_ULONG Hebrew_862Mask = SkTEndian_SwapBE32(1 << (53 - 32));
+ static const SK_OT_ULONG MSDOSIcelandic_861Mask = SkTEndian_SwapBE32(1 << (54 - 32));
+ static const SK_OT_ULONG MSDOSPortuguese_860Mask = SkTEndian_SwapBE32(1 << (55 - 32));
+ static const SK_OT_ULONG IBMTurkish_857Mask = SkTEndian_SwapBE32(1 << (56 - 32));
+ static const SK_OT_ULONG IBMCyrillic_855Mask = SkTEndian_SwapBE32(1 << (57 - 32));
+ static const SK_OT_ULONG Latin2_852Mask = SkTEndian_SwapBE32(1 << (58 - 32));
+ static const SK_OT_ULONG MSDOSBaltic_775Mask = SkTEndian_SwapBE32(1 << (59 - 32));
+ static const SK_OT_ULONG Greek_737Mask = SkTEndian_SwapBE32(1 << (60 - 32));
+ static const SK_OT_ULONG Arabic_708Mask = SkTEndian_SwapBE32(1 << (61 - 32));
+ static const SK_OT_ULONG WELatin1_850Mask = SkTEndian_SwapBE32(1 << (62 - 32));
+ static const SK_OT_ULONG US_437Mask = SkTEndian_SwapBE32(1 << (63 - 32));
+ };
+ SK_OT_ULONG value[2];
+ } raw;
+ } ulCodePageRange;
+ //version2
+ SK_OT_SHORT sxHeight;
+ SK_OT_SHORT sCapHeight;
+ SK_OT_USHORT usDefaultChar;
+ SK_OT_USHORT usBreakChar;
+ SK_OT_USHORT usMaxContext;
+};
+
+#pragma pack(pop)
+
+
+SK_COMPILE_ASSERT(sizeof(SkOTTableOS2_V3) == 96, sizeof_SkOTTableOS2_V3_not_96);
+
+#endif
diff --git a/src/sfnt/SkOTTable_OS_2_V4.h b/src/sfnt/SkOTTable_OS_2_V4.h
new file mode 100644
index 0000000000..fc6ed5daad
--- /dev/null
+++ b/src/sfnt/SkOTTable_OS_2_V4.h
@@ -0,0 +1,585 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkOTTable_OS_2_V4_DEFINED
+#define SkOTTable_OS_2_V4_DEFINED
+
+#include "SkEndian.h"
+#include "SkIBMFamilyClass.h"
+#include "SkOTTableTypes.h"
+#include "SkPanose.h"
+#include "SkTypedEnum.h"
+
+#pragma pack(push, 1)
+
+struct SkOTTableOS2_V4 {
+ SK_OT_USHORT version;
+ static const SK_OT_USHORT VERSION = SkTEndian_SwapBE16(4);
+
+ SK_OT_SHORT xAvgCharWidth;
+ struct WeightClass {
+ SK_TYPED_ENUM(Value, SK_OT_USHORT,
+ ((Thin, SkTEndian_SwapBE16(100)))
+ ((ExtraLight, SkTEndian_SwapBE16(200)))
+ ((Light, SkTEndian_SwapBE16(300)))
+ ((Normal, SkTEndian_SwapBE16(400)))
+ ((Medium, SkTEndian_SwapBE16(500)))
+ ((SemiBold, SkTEndian_SwapBE16(600)))
+ ((Bold, SkTEndian_SwapBE16(700)))
+ ((ExtraBold, SkTEndian_SwapBE16(800)))
+ ((Black, SkTEndian_SwapBE16(900)))
+ SK_SEQ_END,
+ SK_SEQ_END)
+ SK_OT_USHORT value;
+ } usWeightClass;
+ struct WidthClass {
+ SK_TYPED_ENUM(Value, SK_OT_USHORT,
+ ((UltraCondensed, SkTEndian_SwapBE16(1)))
+ ((ExtraCondensed, SkTEndian_SwapBE16(2)))
+ ((Condensed, SkTEndian_SwapBE16(3)))
+ ((SemiCondensed, SkTEndian_SwapBE16(4)))
+ ((Medium, SkTEndian_SwapBE16(5)))
+ ((SemiExpanded, SkTEndian_SwapBE16(6)))
+ ((Expanded, SkTEndian_SwapBE16(7)))
+ ((ExtraExpanded, SkTEndian_SwapBE16(8)))
+ ((UltraExpanded, SkTEndian_SwapBE16(9)))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } usWidthClass;
+ union Type {
+ struct Field {
+ //8-15
+ SK_OT_BYTE_BITFIELD(
+ NoSubsetting,
+ Bitmap,
+ Reserved10,
+ Reserved11,
+ Reserved12,
+ Reserved13,
+ Reserved14,
+ Reserved15)
+ //0-7
+ SK_OT_BYTE_BITFIELD(
+ Reserved00,
+ Restricted,
+ PreviewPrint,
+ Editable,
+ Reserved04,
+ Reserved05,
+ Reserved06,
+ Reserved07)
+ } field;
+ struct Raw {
+ static const SK_OT_USHORT Installable = SkTEndian_SwapBE16(0);
+ static const SK_OT_USHORT RestrictedMask = SkTEndian_SwapBE16(1 << 1);
+ static const SK_OT_USHORT PreviewPrintMask = SkTEndian_SwapBE16(1 << 2);
+ static const SK_OT_USHORT EditableMask = SkTEndian_SwapBE16(1 << 3);
+ static const SK_OT_USHORT NoSubsettingMask = SkTEndian_SwapBE16(1 << 8);
+ static const SK_OT_USHORT BitmapMask = SkTEndian_SwapBE16(1 << 9);
+ SK_OT_USHORT value;
+ } raw;
+ } fsType;
+ SK_OT_SHORT ySubscriptXSize;
+ SK_OT_SHORT ySubscriptYSize;
+ SK_OT_SHORT ySubscriptXOffset;
+ SK_OT_SHORT ySubscriptYOffset;
+ SK_OT_SHORT ySuperscriptXSize;
+ SK_OT_SHORT ySuperscriptYSize;
+ SK_OT_SHORT ySuperscriptXOffset;
+ SK_OT_SHORT ySuperscriptYOffset;
+ SK_OT_SHORT yStrikeoutSize;
+ SK_OT_SHORT yStrikeoutPosition;
+ SkIBMFamilyClass sFamilyClass;
+ SkPanose panose;
+ union UnicodeRange {
+ struct Field {
+ //l0 24-31
+ SK_OT_BYTE_BITFIELD(
+ Thai,
+ Lao,
+ Georgian,
+ Balinese,
+ HangulJamo,
+ LatinExtendedAdditional,
+ GreekExtended,
+ GeneralPunctuation)
+ //l0 16-23
+ SK_OT_BYTE_BITFIELD(
+ Bengali,
+ Gurmukhi,
+ Gujarati,
+ Oriya,
+ Tamil,
+ Telugu,
+ Kannada,
+ Malayalam)
+ //l0 8-15
+ SK_OT_BYTE_BITFIELD(
+ Coptic,
+ Cyrillic,
+ Armenian,
+ Hebrew,
+ Vai,
+ Arabic,
+ NKo,
+ Devanagari)
+ //l0 0-7
+ SK_OT_BYTE_BITFIELD(
+ BasicLatin,
+ Latin1Supplement,
+ LatinExtendedA,
+ LatinExtendedB,
+ IPAExtensions,
+ SpacingModifierLetters,
+ CombiningDiacriticalMarks,
+ GreekAndCoptic)
+
+ //l1 24-31
+ SK_OT_BYTE_BITFIELD(
+ Hangul,
+ NonPlane0,
+ Phoenician,
+ CJKUnifiedIdeographs,
+ PrivateUseArea,
+ CJKCompatibilityIdeographs,
+ AlphabeticPresentationForms,
+ ArabicPresentationFormsA)
+ //l1 16-23
+ SK_OT_BYTE_BITFIELD(
+ CJKSymbolsAndPunctuation,
+ Hiragana,
+ Katakana,
+ Bopomofo,
+ HangulCompatibilityJamo,
+ PhagsPa,
+ EnclosedCJKLettersAndMonths,
+ CJKCompatibility)
+ //l1 8-15
+ SK_OT_BYTE_BITFIELD(
+ ControlPictures,
+ OpticalCharacterRecognition,
+ EnclosedAlphanumerics,
+ BoxDrawing,
+ BlockElements,
+ GeometricShapes,
+ MiscellaneousSymbols,
+ Dingbats)
+ //l1 0-7
+ SK_OT_BYTE_BITFIELD(
+ SuperscriptsAndSubscripts,
+ CurrencySymbols,
+ CombiningDiacriticalMarksForSymbols,
+ LetterlikeSymbols,
+ NumberForms,
+ Arrows,
+ MathematicalOperators,
+ MiscellaneousTechnical)
+
+ //l2 24-31
+ SK_OT_BYTE_BITFIELD(
+ MusicalSymbols,
+ MathematicalAlphanumericSymbols,
+ PrivateUse,
+ VariationSelectors,
+ Tags,
+ Limbu,
+ TaiLe,
+ NewTaiLue)
+ //l2 16-23
+ SK_OT_BYTE_BITFIELD(
+ Khmer,
+ Mongolian,
+ Braille,
+ Yi,
+ Tagalog_Hanunoo_Buhid_Tagbanwa,
+ OldItalic,
+ Gothic,
+ Deseret)
+ //l2 8-15
+ SK_OT_BYTE_BITFIELD(
+ Thaana,
+ Sinhala,
+ Myanmar,
+ Ethiopic,
+ Cherokee,
+ UnifiedCanadianSyllabics,
+ Ogham,
+ Runic)
+ //l2 0-7
+ SK_OT_BYTE_BITFIELD(
+ CombiningHalfMarks,
+ CJKCompatibilityForms,
+ SmallFormVariants,
+ ArabicPresentationFormsB,
+ HalfwidthAndFullwidthForms,
+ Specials,
+ Tibetan,
+ Syriac)
+
+ //l3 24-31
+ SK_OT_BYTE_BITFIELD(
+ PhaistosDisc,
+ Carian_Lycian_Lydian,
+ DominoTiles_MahjongTiles,
+ Reserved123,
+ Reserved124,
+ Reserved125,
+ Reserved126,
+ Reserved127)
+ //l3 16-23
+ SK_OT_BYTE_BITFIELD(
+ Sundanese,
+ Lepcha,
+ OlChiki,
+ Saurashtra,
+ KayahLi,
+ Rejang,
+ Cham,
+ AncientSymbols)
+ //l3 8-15
+ SK_OT_BYTE_BITFIELD(
+ OldPersian,
+ Shavian,
+ Osmanya,
+ CypriotSyllabary,
+ Kharoshthi,
+ TaiXuanJingSymbols,
+ Cuneiform,
+ CountingRodNumerals)
+ //l3 0-7
+ SK_OT_BYTE_BITFIELD(
+ Buginese,
+ Glagolitic,
+ Tifinagh,
+ YijingHexagramSymbols,
+ SylotiNagri,
+ LinearB_AegeanNumbers,
+ AncientGreekNumbers,
+ Ugaritic)
+ } field;
+ struct Raw {
+ struct l0 {
+ static const SK_OT_ULONG BasicLatinMask = SkTEndian_SwapBE32(1 << 0);
+ static const SK_OT_ULONG Latin1SupplementMask = SkTEndian_SwapBE32(1 << 1);
+ static const SK_OT_ULONG LatinExtendedAMask = SkTEndian_SwapBE32(1 << 2);
+ static const SK_OT_ULONG LatinExtendedBMask = SkTEndian_SwapBE32(1 << 3);
+ static const SK_OT_ULONG IPAExtensionsMask = SkTEndian_SwapBE32(1 << 4);
+ static const SK_OT_ULONG SpacingModifierLettersMask = SkTEndian_SwapBE32(1 << 5);
+ static const SK_OT_ULONG CombiningDiacriticalMarksMask = SkTEndian_SwapBE32(1 << 6);
+ static const SK_OT_ULONG GreekAndCopticMask = SkTEndian_SwapBE32(1 << 7);
+ static const SK_OT_ULONG CopticMask = SkTEndian_SwapBE32(1 << 8);
+ static const SK_OT_ULONG CyrillicMask = SkTEndian_SwapBE32(1 << 9);
+ static const SK_OT_ULONG ArmenianMask = SkTEndian_SwapBE32(1 << 10);
+ static const SK_OT_ULONG HebrewMask = SkTEndian_SwapBE32(1 << 11);
+ static const SK_OT_ULONG VaiMask = SkTEndian_SwapBE32(1 << 12);
+ static const SK_OT_ULONG ArabicMask = SkTEndian_SwapBE32(1 << 13);
+ static const SK_OT_ULONG NKoMask = SkTEndian_SwapBE32(1 << 14);
+ static const SK_OT_ULONG DevanagariMask = SkTEndian_SwapBE32(1 << 15);
+ static const SK_OT_ULONG BengaliMask = SkTEndian_SwapBE32(1 << 16);
+ static const SK_OT_ULONG GurmukhiMask = SkTEndian_SwapBE32(1 << 17);
+ static const SK_OT_ULONG GujaratiMask = SkTEndian_SwapBE32(1 << 18);
+ static const SK_OT_ULONG OriyaMask = SkTEndian_SwapBE32(1 << 19);
+ static const SK_OT_ULONG TamilMask = SkTEndian_SwapBE32(1 << 20);
+ static const SK_OT_ULONG TeluguMask = SkTEndian_SwapBE32(1 << 21);
+ static const SK_OT_ULONG KannadaMask = SkTEndian_SwapBE32(1 << 22);
+ static const SK_OT_ULONG MalayalamMask = SkTEndian_SwapBE32(1 << 23);
+ static const SK_OT_ULONG ThaiMask = SkTEndian_SwapBE32(1 << 24);
+ static const SK_OT_ULONG LaoMask = SkTEndian_SwapBE32(1 << 25);
+ static const SK_OT_ULONG GeorgianMask = SkTEndian_SwapBE32(1 << 26);
+ static const SK_OT_ULONG BalineseMask = SkTEndian_SwapBE32(1 << 27);
+ static const SK_OT_ULONG HangulJamoMask = SkTEndian_SwapBE32(1 << 28);
+ static const SK_OT_ULONG LatinExtendedAdditionalMask = SkTEndian_SwapBE32(1 << 29);
+ static const SK_OT_ULONG GreekExtendedMask = SkTEndian_SwapBE32(1 << 30);
+ static const SK_OT_ULONG GeneralPunctuationMask = SkTEndian_SwapBE32(1 << 31);
+ };
+ struct l1 {
+ static const SK_OT_ULONG SuperscriptsAndSubscriptsMask = SkTEndian_SwapBE32(1 << (32 - 32));
+ static const SK_OT_ULONG CurrencySymbolsMask = SkTEndian_SwapBE32(1 << (33 - 32));
+ static const SK_OT_ULONG CombiningDiacriticalMarksForSymbolsMask = SkTEndian_SwapBE32(1 << (34 - 32));
+ static const SK_OT_ULONG LetterlikeSymbolsMask = SkTEndian_SwapBE32(1 << (35 - 32));
+ static const SK_OT_ULONG NumberFormsMask = SkTEndian_SwapBE32(1 << (36 - 32));
+ static const SK_OT_ULONG ArrowsMask = SkTEndian_SwapBE32(1 << (37 - 32));
+ static const SK_OT_ULONG MathematicalOperatorsMask = SkTEndian_SwapBE32(1 << (38 - 32));
+ static const SK_OT_ULONG MiscellaneousTechnicalMask = SkTEndian_SwapBE32(1 << (39 - 32));
+ static const SK_OT_ULONG ControlPicturesMask = SkTEndian_SwapBE32(1 << (40 - 32));
+ static const SK_OT_ULONG OpticalCharacterRecognitionMask = SkTEndian_SwapBE32(1 << (41 - 32));
+ static const SK_OT_ULONG EnclosedAlphanumericsMask = SkTEndian_SwapBE32(1 << (42 - 32));
+ static const SK_OT_ULONG BoxDrawingMask = SkTEndian_SwapBE32(1 << (43 - 32));
+ static const SK_OT_ULONG BlockElementsMask = SkTEndian_SwapBE32(1 << (44 - 32));
+ static const SK_OT_ULONG GeometricShapesMask = SkTEndian_SwapBE32(1 << (45 - 32));
+ static const SK_OT_ULONG MiscellaneousSymbolsMask = SkTEndian_SwapBE32(1 << (46 - 32));
+ static const SK_OT_ULONG DingbatsMask = SkTEndian_SwapBE32(1 << (47 - 32));
+ static const SK_OT_ULONG CJKSymbolsAndPunctuationMask = SkTEndian_SwapBE32(1 << (48 - 32));
+ static const SK_OT_ULONG HiraganaMask = SkTEndian_SwapBE32(1 << (49 - 32));
+ static const SK_OT_ULONG KatakanaMask = SkTEndian_SwapBE32(1 << (50 - 32));
+ static const SK_OT_ULONG BopomofoMask = SkTEndian_SwapBE32(1 << (51 - 32));
+ static const SK_OT_ULONG HangulCompatibilityJamoMask = SkTEndian_SwapBE32(1 << (52 - 32));
+ static const SK_OT_ULONG PhagsPaMask = SkTEndian_SwapBE32(1 << (53 - 32));
+ static const SK_OT_ULONG EnclosedCJKLettersAndMonthsMask = SkTEndian_SwapBE32(1 << (54 - 32));
+ static const SK_OT_ULONG CJKCompatibilityMask = SkTEndian_SwapBE32(1 << (55 - 32));
+ static const SK_OT_ULONG HangulMask = SkTEndian_SwapBE32(1 << (56 - 32));
+ static const SK_OT_ULONG NonPlane0Mask = SkTEndian_SwapBE32(1 << (57 - 32));
+ static const SK_OT_ULONG PhoenicianMask = SkTEndian_SwapBE32(1 << (58 - 32));
+ static const SK_OT_ULONG CJKUnifiedIdeographsMask = SkTEndian_SwapBE32(1 << (59 - 32));
+ static const SK_OT_ULONG PrivateUseAreaMask = SkTEndian_SwapBE32(1 << (60 - 32));
+ static const SK_OT_ULONG CJKCompatibilityIdeographsMask = SkTEndian_SwapBE32(1 << (61 - 32));
+ static const SK_OT_ULONG AlphabeticPresentationFormsMask = SkTEndian_SwapBE32(1 << (62 - 32));
+ static const SK_OT_ULONG ArabicPresentationFormsAMask = SkTEndian_SwapBE32(1 << (63 - 32));
+ };
+ struct l2 {
+ static const SK_OT_ULONG CombiningHalfMarksMask = SkTEndian_SwapBE32(1 << (64 - 64));
+ static const SK_OT_ULONG CJKCompatibilityFormsMask = SkTEndian_SwapBE32(1 << (65 - 64));
+ static const SK_OT_ULONG SmallFormVariantsMask = SkTEndian_SwapBE32(1 << (66 - 64));
+ static const SK_OT_ULONG ArabicPresentationFormsBMask = SkTEndian_SwapBE32(1 << (67 - 64));
+ static const SK_OT_ULONG HalfwidthAndFullwidthFormsMask = SkTEndian_SwapBE32(1 << (68 - 64));
+ static const SK_OT_ULONG SpecialsMask = SkTEndian_SwapBE32(1 << (69 - 64));
+ static const SK_OT_ULONG TibetanMask = SkTEndian_SwapBE32(1 << (70 - 64));
+ static const SK_OT_ULONG SyriacMask = SkTEndian_SwapBE32(1 << (71 - 64));
+ static const SK_OT_ULONG ThaanaMask = SkTEndian_SwapBE32(1 << (72 - 64));
+ static const SK_OT_ULONG SinhalaMask = SkTEndian_SwapBE32(1 << (73 - 64));
+ static const SK_OT_ULONG MyanmarMask = SkTEndian_SwapBE32(1 << (74 - 64));
+ static const SK_OT_ULONG EthiopicMask = SkTEndian_SwapBE32(1 << (75 - 64));
+ static const SK_OT_ULONG CherokeeMask = SkTEndian_SwapBE32(1 << (76 - 64));
+ static const SK_OT_ULONG UnifiedCanadianSyllabicsMask = SkTEndian_SwapBE32(1 << (77 - 64));
+ static const SK_OT_ULONG OghamMask = SkTEndian_SwapBE32(1 << (78 - 64));
+ static const SK_OT_ULONG RunicMask = SkTEndian_SwapBE32(1 << (79 - 64));
+ static const SK_OT_ULONG KhmerMask = SkTEndian_SwapBE32(1 << (80 - 64));
+ static const SK_OT_ULONG MongolianMask = SkTEndian_SwapBE32(1 << (81 - 64));
+ static const SK_OT_ULONG BrailleMask = SkTEndian_SwapBE32(1 << (82 - 64));
+ static const SK_OT_ULONG YiMask = SkTEndian_SwapBE32(1 << (83 - 64));
+ static const SK_OT_ULONG Tagalog_Hanunoo_Buhid_TagbanwaMask = SkTEndian_SwapBE32(1 << (84 - 64));
+ static const SK_OT_ULONG OldItalicMask = SkTEndian_SwapBE32(1 << (85 - 64));
+ static const SK_OT_ULONG GothicMask = SkTEndian_SwapBE32(1 << (86 - 64));
+ static const SK_OT_ULONG DeseretMask = SkTEndian_SwapBE32(1 << (87 - 64));
+ static const SK_OT_ULONG MusicalSymbolsMask = SkTEndian_SwapBE32(1 << (88 - 64));
+ static const SK_OT_ULONG MathematicalAlphanumericSymbolsMask = SkTEndian_SwapBE32(1 << (89 - 64));
+ static const SK_OT_ULONG PrivateUseMask = SkTEndian_SwapBE32(1 << (90 - 64));
+ static const SK_OT_ULONG VariationSelectorsMask = SkTEndian_SwapBE32(1 << (91 - 64));
+ static const SK_OT_ULONG TagsMask = SkTEndian_SwapBE32(1 << (92 - 64));
+ static const SK_OT_ULONG LimbuMask = SkTEndian_SwapBE32(1 << (93 - 64));
+ static const SK_OT_ULONG TaiLeMask = SkTEndian_SwapBE32(1 << (94 - 64));
+ static const SK_OT_ULONG NewTaiLueMask = SkTEndian_SwapBE32(1 << (95 - 64));
+ };
+ struct l3 {
+ static const SK_OT_ULONG BugineseMask = SkTEndian_SwapBE32(1 << (96 - 96));
+ static const SK_OT_ULONG GlagoliticMask = SkTEndian_SwapBE32(1 << (97 - 96));
+ static const SK_OT_ULONG TifinaghMask = SkTEndian_SwapBE32(1 << (98 - 96));
+ static const SK_OT_ULONG YijingHexagramSymbolsMask = SkTEndian_SwapBE32(1 << (99 - 96));
+ static const SK_OT_ULONG SylotiNagriMask = SkTEndian_SwapBE32(1 << (100 - 96));
+ static const SK_OT_ULONG LinearB_AegeanNumbersMask = SkTEndian_SwapBE32(1 << (101 - 96));
+ static const SK_OT_ULONG AncientGreekNumbersMask = SkTEndian_SwapBE32(1 << (102 - 96));
+ static const SK_OT_ULONG UgariticMask = SkTEndian_SwapBE32(1 << (103 - 96));
+ static const SK_OT_ULONG OldPersianMask = SkTEndian_SwapBE32(1 << (104 - 96));
+ static const SK_OT_ULONG ShavianMask = SkTEndian_SwapBE32(1 << (105 - 96));
+ static const SK_OT_ULONG OsmanyaMask = SkTEndian_SwapBE32(1 << (106 - 96));
+ static const SK_OT_ULONG CypriotSyllabaryMask = SkTEndian_SwapBE32(1 << (107 - 96));
+ static const SK_OT_ULONG KharoshthiMask = SkTEndian_SwapBE32(1 << (108 - 96));
+ static const SK_OT_ULONG TaiXuanJingSymbolsMask = SkTEndian_SwapBE32(1 << (109 - 96));
+ static const SK_OT_ULONG CuneiformMask = SkTEndian_SwapBE32(1 << (110 - 96));
+ static const SK_OT_ULONG CountingRodNumeralsMask = SkTEndian_SwapBE32(1 << (111 - 96));
+ static const SK_OT_ULONG SundaneseMask = SkTEndian_SwapBE32(1 << (112 - 96));
+ static const SK_OT_ULONG LepchaMask = SkTEndian_SwapBE32(1 << (113 - 96));
+ static const SK_OT_ULONG OlChikiMask = SkTEndian_SwapBE32(1 << (114 - 96));
+ static const SK_OT_ULONG SaurashtraMask = SkTEndian_SwapBE32(1 << (115 - 96));
+ static const SK_OT_ULONG KayahLiMask = SkTEndian_SwapBE32(1 << (116 - 96));
+ static const SK_OT_ULONG RejangMask = SkTEndian_SwapBE32(1 << (117 - 96));
+ static const SK_OT_ULONG ChamMask = SkTEndian_SwapBE32(1 << (118 - 96));
+ static const SK_OT_ULONG AncientSymbolsMask = SkTEndian_SwapBE32(1 << (119 - 96));
+ static const SK_OT_ULONG PhaistosDiscMask = SkTEndian_SwapBE32(1 << (120 - 96));
+ static const SK_OT_ULONG Carian_Lycian_LydianMask = SkTEndian_SwapBE32(1 << (121 - 96));
+ static const SK_OT_ULONG DominoTiles_MahjongTilesMask = SkTEndian_SwapBE32(1 << (122 - 96));
+ };
+ SK_OT_ULONG value[4];
+ } raw;
+ } ulUnicodeRange;
+ SK_OT_CHAR achVendID[4];
+ union Selection {
+ struct Field {
+ //8-15
+ SK_OT_BYTE_BITFIELD(
+ WWS,
+ Oblique,
+ Reserved10,
+ Reserved11,
+ Reserved12,
+ Reserved13,
+ Reserved14,
+ Reserved15)
+ //0-7
+ SK_OT_BYTE_BITFIELD(
+ Italic,
+ Underscore,
+ Negative,
+ Outlined,
+ Strikeout,
+ Bold,
+ Regular,
+ UseTypoMetrics)
+ } field;
+ struct Raw {
+ static const SK_OT_USHORT ItalicMask = SkTEndian_SwapBE16(1 << 0);
+ static const SK_OT_USHORT UnderscoreMask = SkTEndian_SwapBE16(1 << 1);
+ static const SK_OT_USHORT NegativeMask = SkTEndian_SwapBE16(1 << 2);
+ static const SK_OT_USHORT OutlinedMask = SkTEndian_SwapBE16(1 << 3);
+ static const SK_OT_USHORT StrikeoutMask = SkTEndian_SwapBE16(1 << 4);
+ static const SK_OT_USHORT BoldMask = SkTEndian_SwapBE16(1 << 5);
+ static const SK_OT_USHORT RegularMask = SkTEndian_SwapBE16(1 << 6);
+ static const SK_OT_USHORT UseTypoMetricsMask = SkTEndian_SwapBE16(1 << 7);
+ static const SK_OT_USHORT WWSMask = SkTEndian_SwapBE16(1 << 8);
+ static const SK_OT_USHORT ObliqueMask = SkTEndian_SwapBE16(1 << 9);
+ SK_OT_USHORT value;
+ } raw;
+ } fsSelection;
+ SK_OT_USHORT usFirstCharIndex;
+ SK_OT_USHORT usLastCharIndex;
+ //version0
+ SK_OT_SHORT sTypoAscender;
+ SK_OT_SHORT sTypoDescender;
+ SK_OT_SHORT sTypoLineGap;
+ SK_OT_USHORT usWinAscent;
+ SK_OT_USHORT usWinDescent;
+ //version1
+ union CodePageRange {
+ struct Field {
+ //l0 24-31
+ SK_OT_BYTE_BITFIELD(
+ Reserved24,
+ Reserved25,
+ Reserved26,
+ Reserved27,
+ Reserved28,
+ MacintoshCharacterSet,
+ OEMCharacterSet,
+ SymbolCharacterSet)
+ //l0 16-23
+ SK_OT_BYTE_BITFIELD(
+ Thai_874,
+ JISJapan_932,
+ ChineseSimplified_936,
+ KoreanWansung_949,
+ ChineseTraditional_950,
+ KoreanJohab_1361,
+ Reserved22,
+ Reserved23)
+ //l0 8-15
+ SK_OT_BYTE_BITFIELD(
+ Vietnamese,
+ Reserved09,
+ Reserved10,
+ Reserved11,
+ Reserved12,
+ Reserved13,
+ Reserved14,
+ Reserved15)
+ //l0 0-7
+ SK_OT_BYTE_BITFIELD(
+ Latin1_1252,
+ Latin2EasternEurope_1250,
+ Cyrillic_1251,
+ Greek_1253,
+ Turkish_1254,
+ Hebrew_1255,
+ Arabic_1256,
+ WindowsBaltic_1257)
+
+ //l1 24-31
+ SK_OT_BYTE_BITFIELD(
+ IBMTurkish_857,
+ IBMCyrillic_855,
+ Latin2_852,
+ MSDOSBaltic_775,
+ Greek_737,
+ Arabic_708,
+ WELatin1_850,
+ US_437)
+ //l1 16-23
+ SK_OT_BYTE_BITFIELD(
+ IBMGreek_869,
+ MSDOSRussian_866,
+ MSDOSNordic_865,
+ Arabic_864,
+ MSDOSCanadianFrench_863,
+ Hebrew_862,
+ MSDOSIcelandic_861,
+ MSDOSPortuguese_860)
+ //l1 8-15
+ SK_OT_BYTE_BITFIELD(
+ Reserved40,
+ Reserved41,
+ Reserved42,
+ Reserved43,
+ Reserved44,
+ Reserved45,
+ Reserved46,
+ Reserved47)
+ //l1 0-7
+ SK_OT_BYTE_BITFIELD(
+ Reserved32,
+ Reserved33,
+ Reserved34,
+ Reserved35,
+ Reserved36,
+ Reserved37,
+ Reserved38,
+ Reserved39)
+ } field;
+ struct Raw {
+ struct l0 {
+ static const SK_OT_ULONG Latin1_1252Mask = SkTEndian_SwapBE32(1 << 0);
+ static const SK_OT_ULONG Latin2EasternEurope_1250Mask = SkTEndian_SwapBE32(1 << 1);
+ static const SK_OT_ULONG Cyrillic_1251Mask = SkTEndian_SwapBE32(1 << 2);
+ static const SK_OT_ULONG Greek_1253Mask = SkTEndian_SwapBE32(1 << 3);
+ static const SK_OT_ULONG Turkish_1254Mask = SkTEndian_SwapBE32(1 << 4);
+ static const SK_OT_ULONG Hebrew_1255Mask = SkTEndian_SwapBE32(1 << 5);
+ static const SK_OT_ULONG Arabic_1256Mask = SkTEndian_SwapBE32(1 << 6);
+ static const SK_OT_ULONG WindowsBaltic_1257Mask = SkTEndian_SwapBE32(1 << 7);
+ static const SK_OT_ULONG Vietnamese_1258Mask = SkTEndian_SwapBE32(1 << 8);
+ static const SK_OT_ULONG Thai_874Mask = SkTEndian_SwapBE32(1 << 16);
+ static const SK_OT_ULONG JISJapan_932Mask = SkTEndian_SwapBE32(1 << 17);
+ static const SK_OT_ULONG ChineseSimplified_936Mask = SkTEndian_SwapBE32(1 << 18);
+ static const SK_OT_ULONG KoreanWansung_949Mask = SkTEndian_SwapBE32(1 << 19);
+ static const SK_OT_ULONG ChineseTraditional_950Mask = SkTEndian_SwapBE32(1 << 20);
+ static const SK_OT_ULONG KoreanJohab_1361Mask = SkTEndian_SwapBE32(1 << 21);
+ static const SK_OT_ULONG MacintoshCharacterSetMask = SkTEndian_SwapBE32(1 << 29);
+ static const SK_OT_ULONG OEMCharacterSetMask = SkTEndian_SwapBE32(1 << 30);
+ static const SK_OT_ULONG SymbolCharacterSetMask = SkTEndian_SwapBE32(1 << 31);
+ };
+ struct l1 {
+ static const SK_OT_ULONG IBMGreek_869Mask = SkTEndian_SwapBE32(1 << (48 - 32));
+ static const SK_OT_ULONG MSDOSRussian_866Mask = SkTEndian_SwapBE32(1 << (49 - 32));
+ static const SK_OT_ULONG MSDOSNordic_865Mask = SkTEndian_SwapBE32(1 << (50 - 32));
+ static const SK_OT_ULONG Arabic_864Mask = SkTEndian_SwapBE32(1 << (51 - 32));
+ static const SK_OT_ULONG MSDOSCanadianFrench_863Mask = SkTEndian_SwapBE32(1 << (52 - 32));
+ static const SK_OT_ULONG Hebrew_862Mask = SkTEndian_SwapBE32(1 << (53 - 32));
+ static const SK_OT_ULONG MSDOSIcelandic_861Mask = SkTEndian_SwapBE32(1 << (54 - 32));
+ static const SK_OT_ULONG MSDOSPortuguese_860Mask = SkTEndian_SwapBE32(1 << (55 - 32));
+ static const SK_OT_ULONG IBMTurkish_857Mask = SkTEndian_SwapBE32(1 << (56 - 32));
+ static const SK_OT_ULONG IBMCyrillic_855Mask = SkTEndian_SwapBE32(1 << (57 - 32));
+ static const SK_OT_ULONG Latin2_852Mask = SkTEndian_SwapBE32(1 << (58 - 32));
+ static const SK_OT_ULONG MSDOSBaltic_775Mask = SkTEndian_SwapBE32(1 << (59 - 32));
+ static const SK_OT_ULONG Greek_737Mask = SkTEndian_SwapBE32(1 << (60 - 32));
+ static const SK_OT_ULONG Arabic_708Mask = SkTEndian_SwapBE32(1 << (61 - 32));
+ static const SK_OT_ULONG WELatin1_850Mask = SkTEndian_SwapBE32(1 << (62 - 32));
+ static const SK_OT_ULONG US_437Mask = SkTEndian_SwapBE32(1 << (63 - 32));
+ };
+ SK_OT_ULONG value[2];
+ } raw;
+ } ulCodePageRange;
+ //version2
+ SK_OT_SHORT sxHeight;
+ SK_OT_SHORT sCapHeight;
+ SK_OT_USHORT usDefaultChar;
+ SK_OT_USHORT usBreakChar;
+ SK_OT_USHORT usMaxContext;
+};
+
+#pragma pack(pop)
+
+
+SK_COMPILE_ASSERT(sizeof(SkOTTableOS2_V4) == 96, sizeof_SkOTTableOS2_V4_not_96);
+
+#endif
diff --git a/src/sfnt/SkOTTable_OS_2_VA.h b/src/sfnt/SkOTTable_OS_2_VA.h
new file mode 100644
index 0000000000..146e83b67e
--- /dev/null
+++ b/src/sfnt/SkOTTable_OS_2_VA.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkOTTable_OS_2_VA_DEFINED
+#define SkOTTable_OS_2_VA_DEFINED
+
+#include "SkEndian.h"
+#include "SkIBMFamilyClass.h"
+#include "SkOTTableTypes.h"
+#include "SkPanose.h"
+#include "SkTypedEnum.h"
+
+#pragma pack(push, 1)
+
+//Original V0 TT
+struct SkOTTableOS2_VA {
+ SK_OT_USHORT version;
+ //SkOTTableOS2_VA::VERSION and SkOTTableOS2_V0::VERSION are both 0.
+ //The only way to differentiate these two versions is by the size of the table.
+ static const SK_OT_USHORT VERSION = SkTEndian_SwapBE16(0);
+
+ SK_OT_SHORT xAvgCharWidth;
+ struct WeightClass {
+ SK_TYPED_ENUM(Value, SK_OT_USHORT,
+ ((UltraLight, SkTEndian_SwapBE16(1)))
+ ((ExtraLight, SkTEndian_SwapBE16(2)))
+ ((Light, SkTEndian_SwapBE16(3)))
+ ((SemiLight, SkTEndian_SwapBE16(4)))
+ ((Medium, SkTEndian_SwapBE16(5)))
+ ((SemiBold, SkTEndian_SwapBE16(6)))
+ ((Bold, SkTEndian_SwapBE16(7)))
+ ((ExtraBold, SkTEndian_SwapBE16(8)))
+ ((UltraBold, SkTEndian_SwapBE16(9)))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } usWeightClass;
+ struct WidthClass {
+ SK_TYPED_ENUM(Value, SK_OT_USHORT,
+ ((UltraCondensed, SkTEndian_SwapBE16(1)))
+ ((ExtraCondensed, SkTEndian_SwapBE16(2)))
+ ((Condensed, SkTEndian_SwapBE16(3)))
+ ((SemiCondensed, SkTEndian_SwapBE16(4)))
+ ((Medium, SkTEndian_SwapBE16(5)))
+ ((SemiExpanded, SkTEndian_SwapBE16(6)))
+ ((Expanded, SkTEndian_SwapBE16(7)))
+ ((ExtraExpanded, SkTEndian_SwapBE16(8)))
+ ((UltraExpanded, SkTEndian_SwapBE16(9)))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } usWidthClass;
+ union Type {
+ struct Field {
+ //8-15
+ SK_OT_BYTE_BITFIELD(
+ Reserved08,
+ Reserved09,
+ Reserved10,
+ Reserved11,
+ Reserved12,
+ Reserved13,
+ Reserved14,
+ Reserved15)
+ //0-7
+ SK_OT_BYTE_BITFIELD(
+ Reserved00,
+ Restricted,
+ PreviewPrint,
+ Editable,
+ Reserved04,
+ Reserved05,
+ Reserved06,
+ Reserved07)
+ } field;
+ struct Raw {
+ static const SK_OT_USHORT Installable = SkTEndian_SwapBE16(0);
+ static const SK_OT_USHORT RestrictedMask = SkTEndian_SwapBE16(1 << 1);
+ static const SK_OT_USHORT PreviewPrintMask = SkTEndian_SwapBE16(1 << 2);
+ static const SK_OT_USHORT EditableMask = SkTEndian_SwapBE16(1 << 3);
+ SK_OT_USHORT value;
+ } raw;
+ } fsType;
+ SK_OT_SHORT ySubscriptXSize;
+ SK_OT_SHORT ySubscriptYSize;
+ SK_OT_SHORT ySubscriptXOffset;
+ SK_OT_SHORT ySubscriptYOffset;
+ SK_OT_SHORT ySuperscriptXSize;
+ SK_OT_SHORT ySuperscriptYSize;
+ SK_OT_SHORT ySuperscriptXOffset;
+ SK_OT_SHORT ySuperscriptYOffset;
+ SK_OT_SHORT yStrikeoutSize;
+ SK_OT_SHORT yStrikeoutPosition;
+ SkIBMFamilyClass sFamilyClass;
+ SkPanose panose;
+ SK_OT_ULONG ulCharRange[4];
+ SK_OT_CHAR achVendID[4];
+ union Selection {
+ struct Field {
+ //8-15
+ SK_OT_BYTE_BITFIELD(
+ Reserved08,
+ Reserved09,
+ Reserved10,
+ Reserved11,
+ Reserved12,
+ Reserved13,
+ Reserved14,
+ Reserved15)
+ //0-7
+ SK_OT_BYTE_BITFIELD(
+ Italic,
+ Underscore,
+ Negative,
+ Outlined,
+ Strikeout,
+ Bold,
+ Reserved06,
+ Reserved07)
+ } field;
+ struct Raw {
+ static const SK_OT_USHORT ItalicMask = SkTEndian_SwapBE16(1 << 0);
+ static const SK_OT_USHORT UnderscoreMask = SkTEndian_SwapBE16(1 << 1);
+ static const SK_OT_USHORT NegativeMask = SkTEndian_SwapBE16(1 << 2);
+ static const SK_OT_USHORT OutlinedMask = SkTEndian_SwapBE16(1 << 3);
+ static const SK_OT_USHORT StrikeoutMask = SkTEndian_SwapBE16(1 << 4);
+ static const SK_OT_USHORT BoldMask = SkTEndian_SwapBE16(1 << 5);
+ SK_OT_USHORT value;
+ } raw;
+ } fsSelection;
+ SK_OT_USHORT usFirstCharIndex;
+ SK_OT_USHORT usLastCharIndex;
+};
+
+#pragma pack(pop)
+
+
+SK_COMPILE_ASSERT(sizeof(SkOTTableOS2_VA) == 68, sizeof_SkOTTableOS2_VA_not_68);
+
+#endif
diff --git a/src/sfnt/SkOTTable_glyf.h b/src/sfnt/SkOTTable_glyf.h
new file mode 100644
index 0000000000..30c99f62fa
--- /dev/null
+++ b/src/sfnt/SkOTTable_glyf.h
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkOTTable_glyf_DEFINED
+#define SkOTTable_glyf_DEFINED
+
+#include "SkEndian.h"
+#include "SkOTTableTypes.h"
+#include "SkOTTable_head.h"
+#include "SkOTTable_loca.h"
+#include "SkTypedEnum.h"
+
+#pragma pack(push, 1)
+
+struct SkOTTableGlyphData;
+
+extern uint8_t const * const SK_OT_GlyphData_NoOutline;
+
+struct SkOTTableGlyph {
+ static const SK_OT_CHAR TAG0 = 'g';
+ static const SK_OT_CHAR TAG1 = 'l';
+ static const SK_OT_CHAR TAG2 = 'y';
+ static const SK_OT_CHAR TAG3 = 'f';
+ static const SK_OT_ULONG TAG = SkOTTableTAG<SkOTTableGlyph>::value;
+
+ class Iterator {
+ public:
+ Iterator(const SkOTTableGlyph& glyf,
+ const SkOTTableIndexToLocation& loca,
+ SkOTTableHead::IndexToLocFormat locaFormat)
+ : fGlyf(glyf)
+ , fLocaFormat(SkOTTableHead::IndexToLocFormat::ShortOffsets == locaFormat.value ? 0 : 1)
+ , fCurrentGlyphOffset(0)
+ { fLocaPtr.shortOffset = reinterpret_cast<const SK_OT_USHORT*>(&loca); }
+
+ void advance(uint16_t num) {
+ fLocaPtr.shortOffset += num << fLocaFormat;
+ fCurrentGlyphOffset = fLocaFormat ? SkEndian_SwapBE32(*fLocaPtr.longOffset)
+ : SkEndian_SwapBE16(*fLocaPtr.shortOffset) << 1;
+ }
+ const SkOTTableGlyphData* next() {
+ uint32_t previousGlyphOffset = fCurrentGlyphOffset;
+ advance(1);
+ if (previousGlyphOffset == fCurrentGlyphOffset) {
+ return reinterpret_cast<const SkOTTableGlyphData*>(&SK_OT_GlyphData_NoOutline);
+ } else {
+ return reinterpret_cast<const SkOTTableGlyphData*>(
+ reinterpret_cast<const SK_OT_BYTE*>(&fGlyf) + previousGlyphOffset
+ );
+ }
+ }
+ private:
+ const SkOTTableGlyph& fGlyf;
+ uint16_t fLocaFormat; //0 or 1
+ uint32_t fCurrentGlyphOffset;
+ union LocaPtr {
+ const SK_OT_USHORT* shortOffset;
+ const SK_OT_ULONG* longOffset;
+ } fLocaPtr;
+ };
+};
+
+struct SkOTTableGlyphData {
+ SK_OT_SHORT numberOfContours; //== -1 Composite, > 0 Simple
+ SK_OT_FWORD xMin;
+ SK_OT_FWORD yMin;
+ SK_OT_FWORD xMax;
+ SK_OT_FWORD yMax;
+
+ struct Simple {
+ SK_OT_USHORT endPtsOfContours[1/*numberOfContours*/];
+
+ struct Instructions {
+ SK_OT_USHORT length;
+ SK_OT_BYTE data[1/*length*/];
+ };
+
+ union Flags {
+ struct Field {
+ SK_OT_BYTE_BITFIELD(
+ OnCurve,
+ xShortVector,
+ yShortVector,
+ Repeat,
+ xIsSame_xShortVectorPositive,
+ yIsSame_yShortVectorPositive,
+ Reserved6,
+ Reserved7)
+ } field;
+ struct Raw {
+ static const SK_OT_USHORT OnCurveMask = SkTEndian_SwapBE16(1 << 0);
+ static const SK_OT_USHORT xShortVectorMask = SkTEndian_SwapBE16(1 << 1);
+ static const SK_OT_USHORT yShortVectorMask = SkTEndian_SwapBE16(1 << 2);
+ static const SK_OT_USHORT RepeatMask = SkTEndian_SwapBE16(1 << 3);
+ static const SK_OT_USHORT xIsSame_xShortVectorPositiveMask = SkTEndian_SwapBE16(1 << 4);
+ static const SK_OT_USHORT yIsSame_yShortVectorPositiveMask = SkTEndian_SwapBE16(1 << 5);
+ SK_OT_BYTE value;
+ } raw;
+ };
+
+ //xCoordinates
+ //yCoordinates
+ };
+
+ struct Composite {
+ struct Component {
+ union Flags {
+ struct Field {
+ //8-15
+ SK_OT_BYTE_BITFIELD(
+ WE_HAVE_INSTRUCTIONS,
+ USE_MY_METRICS,
+ OVERLAP_COMPOUND,
+ SCALED_COMPONENT_OFFSET,
+ UNSCALED_COMPONENT_OFFSET,
+ Reserved13,
+ Reserved14,
+ Reserved15)
+ //0-7
+ SK_OT_BYTE_BITFIELD(
+ ARG_1_AND_2_ARE_WORDS,
+ ARGS_ARE_XY_VALUES,
+ ROUND_XY_TO_GRID,
+ WE_HAVE_A_SCALE,
+ RESERVED,
+ MORE_COMPONENTS,
+ WE_HAVE_AN_X_AND_Y_SCALE,
+ WE_HAVE_A_TWO_BY_TWO)
+ } field;
+ struct Raw {
+ static const SK_OT_USHORT ARG_1_AND_2_ARE_WORDS_Mask = SkTEndian_SwapBE16(1 << 0);
+ static const SK_OT_USHORT ARGS_ARE_XY_VALUES_Mask = SkTEndian_SwapBE16(1 << 1);
+ static const SK_OT_USHORT ROUND_XY_TO_GRID_Mask = SkTEndian_SwapBE16(1 << 2);
+ static const SK_OT_USHORT WE_HAVE_A_SCALE_Mask = SkTEndian_SwapBE16(1 << 3);
+ static const SK_OT_USHORT RESERVED_Mask = SkTEndian_SwapBE16(1 << 4);
+ static const SK_OT_USHORT MORE_COMPONENTS_Mask = SkTEndian_SwapBE16(1 << 5);
+ static const SK_OT_USHORT WE_HAVE_AN_X_AND_Y_SCALE_Mask = SkTEndian_SwapBE16(1 << 6);
+ static const SK_OT_USHORT WE_HAVE_A_TWO_BY_TWO_Mask = SkTEndian_SwapBE16(1 << 7);
+
+ static const SK_OT_USHORT WE_HAVE_INSTRUCTIONS_Mask = SkTEndian_SwapBE16(1 << 8);
+ static const SK_OT_USHORT USE_MY_METRICS_Mask = SkTEndian_SwapBE16(1 << 9);
+ static const SK_OT_USHORT OVERLAP_COMPOUND_Mask = SkTEndian_SwapBE16(1 << 10);
+ static const SK_OT_USHORT SCALED_COMPONENT_OFFSET_Mask = SkTEndian_SwapBE16(1 << 11);
+ static const SK_OT_USHORT UNSCALED_COMPONENT_OFFSET_mask = SkTEndian_SwapBE16(1 << 12);
+ //Reserved
+ //Reserved
+ //Reserved
+ SK_OT_USHORT value;
+ } raw;
+ } flags;
+ SK_OT_USHORT glyphIndex;
+ union Transform {
+ union Matrix {
+ /** !WE_HAVE_A_SCALE & !WE_HAVE_AN_X_AND_Y_SCALE & !WE_HAVE_A_TWO_BY_TWO */
+ struct None { } none;
+ /** WE_HAVE_A_SCALE */
+ struct Scale {
+ SK_OT_F2DOT14 a_d;
+ } scale;
+ /** WE_HAVE_AN_X_AND_Y_SCALE */
+ struct ScaleXY {
+ SK_OT_F2DOT14 a;
+ SK_OT_F2DOT14 d;
+ } scaleXY;
+ /** WE_HAVE_A_TWO_BY_TWO */
+ struct TwoByTwo {
+ SK_OT_F2DOT14 a;
+ SK_OT_F2DOT14 b;
+ SK_OT_F2DOT14 c;
+ SK_OT_F2DOT14 d;
+ } twoByTwo;
+ };
+ /** ARG_1_AND_2_ARE_WORDS & ARGS_ARE_XY_VALUES */
+ struct WordValue {
+ SK_OT_FWORD e;
+ SK_OT_FWORD f;
+ SkOTTableGlyphData::Composite::Component::Transform::Matrix matrix;
+ } wordValue;
+ /** !ARG_1_AND_2_ARE_WORDS & ARGS_ARE_XY_VALUES */
+ struct ByteValue {
+ SK_OT_CHAR e;
+ SK_OT_CHAR f;
+ SkOTTableGlyphData::Composite::Component::Transform::Matrix matrix;
+ } byteValue;
+ /** ARG_1_AND_2_ARE_WORDS & !ARGS_ARE_XY_VALUES */
+ struct WordIndex {
+ SK_OT_USHORT compoundPointIndex;
+ SK_OT_USHORT componentPointIndex;
+ SkOTTableGlyphData::Composite::Component::Transform::Matrix matrix;
+ } wordIndex;
+ /** !ARG_1_AND_2_ARE_WORDS & !ARGS_ARE_XY_VALUES */
+ struct ByteIndex {
+ SK_OT_BYTE compoundPointIndex;
+ SK_OT_BYTE componentPointIndex;
+ SkOTTableGlyphData::Composite::Component::Transform::Matrix matrix;
+ } byteIndex;
+ } transform;
+ } component;//[] last element does not set MORE_COMPONENTS
+
+ /** Comes after the last Component if the last component has WE_HAVE_INSTR. */
+ struct Instructions {
+ SK_OT_USHORT length;
+ SK_OT_BYTE data[1/*length*/];
+ };
+ };
+};
+
+#pragma pack(pop)
+
+#endif
diff --git a/src/sfnt/SkOTTable_loca.h b/src/sfnt/SkOTTable_loca.h
new file mode 100644
index 0000000000..586daf1d4d
--- /dev/null
+++ b/src/sfnt/SkOTTable_loca.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkOTTable_loca_DEFINED
+#define SkOTTable_loca_DEFINED
+
+#include "SkEndian.h"
+#include "SkOTTableTypes.h"
+
+#pragma pack(push, 1)
+
+struct SkOTTableIndexToLocation {
+ static const SK_OT_CHAR TAG0 = 'l';
+ static const SK_OT_CHAR TAG1 = 'o';
+ static const SK_OT_CHAR TAG2 = 'c';
+ static const SK_OT_CHAR TAG3 = 'a';
+ static const SK_OT_ULONG TAG = SkOTTableTAG<SkOTTableIndexToLocation>::value;
+
+ union Offsets {
+ SK_OT_USHORT shortOffset[1];
+ SK_OT_ULONG longOffset[1];
+ } offsets;
+};
+
+#pragma pack(pop)
+
+#endif
diff --git a/src/sfnt/SkOTTable_maxp.h b/src/sfnt/SkOTTable_maxp.h
new file mode 100644
index 0000000000..d7feac6986
--- /dev/null
+++ b/src/sfnt/SkOTTable_maxp.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkOTTable_maxp_DEFINED
+#define SkOTTable_maxp_DEFINED
+
+#include "SkOTTableTypes.h"
+#include "SkOTTable_maxp_CFF.h"
+#include "SkOTTable_maxp_TT.h"
+
+#pragma pack(push, 1)
+
+struct SkOTTableMaximumProfile {
+ static const SK_OT_CHAR TAG0 = 'm';
+ static const SK_OT_CHAR TAG1 = 'a';
+ static const SK_OT_CHAR TAG2 = 'x';
+ static const SK_OT_CHAR TAG3 = 'p';
+ static const SK_OT_ULONG TAG = SkOTTableTAG<SkOTTableMaximumProfile>::value;
+
+ union Version {
+ SK_OT_Fixed version;
+
+ struct CFF : SkOTTableMaximumProfile_CFF { } cff;
+ struct TT : SkOTTableMaximumProfile_TT { } tt;
+ } version;
+};
+
+#pragma pack(pop)
+
+#endif
diff --git a/src/sfnt/SkOTTable_maxp_CFF.h b/src/sfnt/SkOTTable_maxp_CFF.h
new file mode 100644
index 0000000000..873fb66c9f
--- /dev/null
+++ b/src/sfnt/SkOTTable_maxp_CFF.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkOTTable_maxp_CFF_DEFINED
+#define SkOTTable_maxp_CFF_DEFINED
+
+#include "SkEndian.h"
+#include "SkOTTableTypes.h"
+
+#pragma pack(push, 1)
+
+struct SkOTTableMaximumProfile_CFF {
+ SK_OT_Fixed version;
+ static const SK_OT_Fixed VERSION = SkTEndian_SwapBE32(0x00005000);
+
+ SK_OT_USHORT numGlyphs;
+};
+
+#pragma pack(pop)
+
+
+#include <stddef.h>
+SK_COMPILE_ASSERT(offsetof(SkOTTableMaximumProfile_CFF, numGlyphs) == 4, SkOTTableHead_glyphDataFormat_not_at_2);
+SK_COMPILE_ASSERT(sizeof(SkOTTableMaximumProfile_CFF) == 6, sizeof_SkOTTableHead_not_4);
+
+#endif
diff --git a/src/sfnt/SkOTTable_maxp_TT.h b/src/sfnt/SkOTTable_maxp_TT.h
new file mode 100644
index 0000000000..ad472a1f16
--- /dev/null
+++ b/src/sfnt/SkOTTable_maxp_TT.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkOTTable_maxp_TT_DEFINED
+#define SkOTTable_maxp_TT_DEFINED
+
+#include "SkEndian.h"
+#include "SkOTTableTypes.h"
+#include "SkTypedEnum.h"
+
+#pragma pack(push, 1)
+
+struct SkOTTableMaximumProfile_TT {
+ SK_OT_Fixed version;
+ static const SK_OT_Fixed VERSION = SkTEndian_SwapBE32(0x00010000);
+
+ SK_OT_USHORT numGlyphs;
+ SK_OT_USHORT maxPoints;
+ SK_OT_USHORT maxContours;
+ SK_OT_USHORT maxCompositePoints;
+ SK_OT_USHORT maxCompositeContours;
+ struct MaxZones {
+ SK_TYPED_ENUM(Value, SK_OT_SHORT,
+ ((DoesNotUseTwilightZone, SkTEndian_SwapBE16(1)))
+ ((UsesTwilightZone, SkTEndian_SwapBE16(2)))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } maxZones;
+ SK_OT_USHORT maxTwilightPoints;
+ SK_OT_USHORT maxStorage;
+ SK_OT_USHORT maxFunctionDefs;
+ SK_OT_USHORT maxInstructionDefs;
+ SK_OT_USHORT maxStackElements;
+ SK_OT_USHORT maxSizeOfInstructions;
+ SK_OT_USHORT maxComponentDepth;
+};
+
+#pragma pack(pop)
+
+
+#include <stddef.h>
+SK_COMPILE_ASSERT(offsetof(SkOTTableMaximumProfile_TT, maxComponentDepth) == 28, SkOTTableMaximumProfile_TT_maxComponentDepth_not_at_26);
+SK_COMPILE_ASSERT(sizeof(SkOTTableMaximumProfile_TT) == 30, sizeof_SkOTTableMaximumProfile_TT_not_28);
+
+#endif
diff --git a/src/sfnt/SkOTUtils.cpp b/src/sfnt/SkOTUtils.cpp
new file mode 100644
index 0000000000..0247cdad26
--- /dev/null
+++ b/src/sfnt/SkOTUtils.cpp
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkData.h"
+#include "SkEndian.h"
+#include "SkSFNTHeader.h"
+#include "SkStream.h"
+#include "SkOTTable_head.h"
+#include "SkOTTable_name.h"
+#include "SkOTTableTypes.h"
+#include "SkOTUtils.h"
+
+extern const uint8_t SK_OT_GlyphData_NoOutline[] = {
+ 0x0,0x0, //SkOTTableGlyphData::numberOfContours
+ 0x0,0x0, //SkOTTableGlyphData::xMin
+ 0x0,0x0, //SkOTTableGlyphData::yMin
+ 0x0,0x0, //SkOTTableGlyphData::xMax
+ 0x0,0x0, //SkOTTableGlyphData::yMax
+
+ 0x0,0x0, //SkOTTableGlyphDataInstructions::length
+};
+
+uint32_t SkOTUtils::CalcTableChecksum(SK_OT_ULONG *data, size_t length) {
+ uint32_t sum = 0;
+ SK_OT_ULONG *dataEnd = data + ((length + 3) & ~3) / sizeof(SK_OT_ULONG);
+ for (; data < dataEnd; ++data) {
+ sum += SkEndian_SwapBE32(*data);
+ }
+ return sum;
+}
+
+SkData* SkOTUtils::RenameFont(SkStream* fontData, const char* fontName, int fontNameLen) {
+
+ // Get the sfnt header.
+ SkSFNTHeader sfntHeader;
+ if (fontData->read(&sfntHeader, sizeof(sfntHeader)) < sizeof(sfntHeader)) {
+ return NULL;
+ }
+
+ // Find the existing 'name' table.
+ int tableIndex;
+ SkSFNTHeader::TableDirectoryEntry tableEntry;
+ int numTables = SkEndian_SwapBE16(sfntHeader.numTables);
+ for (tableIndex = 0; tableIndex < numTables; ++tableIndex) {
+ if (fontData->read(&tableEntry, sizeof(tableEntry)) < sizeof(tableEntry)) {
+ return NULL;
+ }
+ if (SkOTTableName::TAG == tableEntry.tag) {
+ break;
+ }
+ }
+ if (tableIndex == numTables) {
+ return NULL;
+ }
+
+ if (!fontData->rewind()) {
+ return NULL;
+ }
+
+ // The required 'name' record types: Family, Style, Unique, Full and PostScript.
+ const SkOTTableNameRecord::NameID::Predefined::Value namesToCreate[] = {
+ SkOTTableNameRecord::NameID::Predefined::FontFamilyName,
+ SkOTTableNameRecord::NameID::Predefined::FontSubfamilyName,
+ SkOTTableNameRecord::NameID::Predefined::UniqueFontIdentifier,
+ SkOTTableNameRecord::NameID::Predefined::FullFontName,
+ SkOTTableNameRecord::NameID::Predefined::PostscriptName,
+ };
+ const int namesCount = SK_ARRAY_COUNT(namesToCreate);
+
+ // Copy the data, leaving out the old name table.
+ // In theory, we could also remove the DSIG table if it exists.
+ size_t nameTableLogicalSize = sizeof(SkOTTableName) + (namesCount * sizeof(SkOTTableNameRecord)) + (fontNameLen * sizeof(wchar_t));
+ size_t nameTablePhysicalSize = (nameTableLogicalSize + 3) & ~3; // Rounded up to a multiple of 4.
+
+ size_t oldNameTablePhysicalSize = (SkEndian_SwapBE32(tableEntry.logicalLength) + 3) & ~3; // Rounded up to a multiple of 4.
+ size_t oldNameTableOffset = SkEndian_SwapBE32(tableEntry.offset);
+
+ //originalDataSize is the size of the original data without the name table.
+ size_t originalDataSize = fontData->getLength() - oldNameTablePhysicalSize;
+ size_t newDataSize = originalDataSize + nameTablePhysicalSize;
+
+ SK_OT_BYTE* data = static_cast<SK_OT_BYTE*>(sk_malloc_throw(newDataSize));
+ SkAutoTUnref<SkData> rewrittenFontData(SkData::NewFromMalloc(data, newDataSize));
+
+ if (fontData->read(data, oldNameTableOffset) < oldNameTableOffset) {
+ return NULL;
+ }
+ if (fontData->skip(oldNameTablePhysicalSize) < oldNameTablePhysicalSize) {
+ return NULL;
+ }
+ if (fontData->read(data + oldNameTableOffset, originalDataSize - oldNameTableOffset) < originalDataSize - oldNameTableOffset) {
+ return NULL;
+ }
+
+ //Fix up the offsets of the directory entries after the old 'name' table entry.
+ SkSFNTHeader::TableDirectoryEntry* currentEntry = reinterpret_cast<SkSFNTHeader::TableDirectoryEntry*>(data + sizeof(SkSFNTHeader));
+ SkSFNTHeader::TableDirectoryEntry* endEntry = currentEntry + numTables;
+ SkSFNTHeader::TableDirectoryEntry* headTableEntry = NULL;
+ for (; currentEntry < endEntry; ++currentEntry) {
+ uint32_t oldOffset = SkEndian_SwapBE32(currentEntry->offset);
+ if (oldOffset > oldNameTableOffset) {
+ currentEntry->offset = SkEndian_SwapBE32(oldOffset - oldNameTablePhysicalSize);
+ }
+ if (SkOTTableHead::TAG == currentEntry->tag) {
+ headTableEntry = currentEntry;
+ }
+ }
+
+ // Make the table directory entry point to the new 'name' table.
+ SkSFNTHeader::TableDirectoryEntry* nameTableEntry = reinterpret_cast<SkSFNTHeader::TableDirectoryEntry*>(data + sizeof(SkSFNTHeader)) + tableIndex;
+ nameTableEntry->logicalLength = SkEndian_SwapBE32(nameTableLogicalSize);
+ nameTableEntry->offset = SkEndian_SwapBE32(originalDataSize);
+
+ // Write the new 'name' table after the original font data.
+ SkOTTableName* nameTable = reinterpret_cast<SkOTTableName*>(data + originalDataSize);
+ unsigned short stringOffset = sizeof(SkOTTableName) + (namesCount * sizeof(SkOTTableNameRecord));
+ nameTable->format = SkOTTableName::format_0;
+ nameTable->count = SkEndian_SwapBE16(namesCount);
+ nameTable->stringOffset = SkEndian_SwapBE16(stringOffset);
+
+ SkOTTableNameRecord* nameRecords = reinterpret_cast<SkOTTableNameRecord*>(data + originalDataSize + sizeof(SkOTTableName));
+ for (int i = 0; i < namesCount; ++i) {
+ nameRecords[i].platformID.value = SkOTTableNameRecord::PlatformID::Windows;
+ nameRecords[i].encodingID.windows.value = SkOTTableNameRecord::EncodingID::Windows::UnicodeBMPUCS2;
+ nameRecords[i].languageID.windows.value = SkOTTableNameRecord::LanguageID::Windows::English_UnitedStates;
+ nameRecords[i].nameID.predefined.value = namesToCreate[i];
+ nameRecords[i].offset = SkEndian_SwapBE16(0);
+ nameRecords[i].length = SkEndian_SwapBE16(fontNameLen * sizeof(wchar_t));
+ }
+
+ SK_OT_USHORT* nameString = reinterpret_cast<SK_OT_USHORT*>(data + originalDataSize + stringOffset);
+ for (int i = 0; i < fontNameLen; ++i) {
+ nameString[i] = SkEndian_SwapBE16(fontName[i]);
+ }
+
+ unsigned char* logical = data + originalDataSize + nameTableLogicalSize;
+ unsigned char* physical = data + originalDataSize + nameTablePhysicalSize;
+ for (; logical < physical; ++logical) {
+ *logical = 0;
+ }
+
+ // Update the table checksum in the directory entry.
+ nameTableEntry->checksum = SkEndian_SwapBE32(SkOTUtils::CalcTableChecksum(reinterpret_cast<SK_OT_ULONG*>(nameTable), nameTableLogicalSize));
+
+ // Update the checksum adjustment in the head table.
+ if (headTableEntry) {
+ size_t headTableOffset = SkEndian_SwapBE32(headTableEntry->offset);
+ if (headTableOffset + sizeof(SkOTTableHead) < originalDataSize) {
+ SkOTTableHead* headTable = reinterpret_cast<SkOTTableHead*>(data + headTableOffset);
+ headTable->checksumAdjustment = SkEndian_SwapBE32(0);
+ uint32_t unadjustedFontChecksum = SkOTUtils::CalcTableChecksum(reinterpret_cast<SK_OT_ULONG*>(data), originalDataSize + nameTablePhysicalSize);
+ headTable->checksumAdjustment = SkEndian_SwapBE32(SkOTTableHead::fontChecksum - unadjustedFontChecksum);
+ }
+ }
+
+ return rewrittenFontData.detach();
+}
diff --git a/src/sfnt/SkOTUtils.h b/src/sfnt/SkOTUtils.h
new file mode 100644
index 0000000000..3c5ada25eb
--- /dev/null
+++ b/src/sfnt/SkOTUtils.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkOTUtils_DEFINED
+#define SkOTUtils_DEFINED
+
+#include "SkOTTableTypes.h"
+class SkData;
+class SkStream;
+
+struct SkOTUtils {
+ /**
+ * Calculates the OpenType checksum for data.
+ */
+ static uint32_t CalcTableChecksum(SK_OT_ULONG *data, size_t length);
+
+ /**
+ * Renames an sfnt font. On failure (invalid data or not an sfnt font)
+ * returns NULL.
+ *
+ * Essentially, this removes any existing 'name' table and replaces it
+ * with a new one in which FontFamilyName, FontSubfamilyName,
+ * UniqueFontIdentifier, FullFontName, and PostscriptName are fontName.
+ *
+ * The new 'name' table records will be written with the Windows,
+ * UnicodeBMPUCS2, and English_UnitedStates settings.
+ *
+ * fontName and fontNameLen must be specified in terms of ASCII chars.
+ */
+ static SkData* RenameFont(SkStream* fontData, const char* fontName, int fontNameLen);
+};
+
+#endif
diff --git a/src/sfnt/SkSFNTHeader.h b/src/sfnt/SkSFNTHeader.h
new file mode 100644
index 0000000000..9071696c99
--- /dev/null
+++ b/src/sfnt/SkSFNTHeader.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkSFNTHeader_DEFINED
+#define SkSFNTHeader_DEFINED
+
+#include "SkEndian.h"
+#include "SkOTTableTypes.h"
+
+//All SK_SFNT_ prefixed types should be considered as big endian.
+typedef uint16_t SK_SFNT_USHORT;
+typedef uint32_t SK_SFNT_ULONG;
+
+#pragma pack(push, 1)
+
+struct SkSFNTHeader {
+ SK_SFNT_ULONG fontType;
+ struct fontType_WindowsTrueType {
+ static const SK_OT_CHAR TAG0 = 0;
+ static const SK_OT_CHAR TAG1 = 1;
+ static const SK_OT_CHAR TAG2 = 0;
+ static const SK_OT_CHAR TAG3 = 0;
+ static const SK_OT_ULONG TAG = SkOTTableTAG<fontType_WindowsTrueType>::value;
+ };
+ struct fontType_MacTrueType {
+ static const SK_OT_CHAR TAG0 = 't';
+ static const SK_OT_CHAR TAG1 = 'r';
+ static const SK_OT_CHAR TAG2 = 'u';
+ static const SK_OT_CHAR TAG3 = 'e';
+ static const SK_OT_ULONG TAG = SkOTTableTAG<fontType_MacTrueType>::value;
+ };
+ struct fontType_PostScript {
+ static const SK_OT_CHAR TAG0 = 't';
+ static const SK_OT_CHAR TAG1 = 'y';
+ static const SK_OT_CHAR TAG2 = 'p';
+ static const SK_OT_CHAR TAG3 = '1';
+ static const SK_OT_ULONG TAG = SkOTTableTAG<fontType_PostScript>::value;
+ };
+ struct fontType_OpenTypeCFF {
+ static const SK_OT_CHAR TAG0 = 'O';
+ static const SK_OT_CHAR TAG1 = 'T';
+ static const SK_OT_CHAR TAG2 = 'T';
+ static const SK_OT_CHAR TAG3 = 'O';
+ static const SK_OT_ULONG TAG = SkOTTableTAG<fontType_OpenTypeCFF>::value;
+ };
+
+ SK_SFNT_USHORT numTables;
+ SK_SFNT_USHORT searchRange;
+ SK_SFNT_USHORT entrySelector;
+ SK_SFNT_USHORT rangeShift;
+
+ struct TableDirectoryEntry {
+ SK_SFNT_ULONG tag;
+ SK_SFNT_ULONG checksum;
+ SK_SFNT_ULONG offset; //From beginning of header.
+ SK_SFNT_ULONG logicalLength;
+ }; //tableDirectoryEntries[numTables]
+};
+
+#pragma pack(pop)
+
+
+SK_COMPILE_ASSERT(sizeof(SkSFNTHeader) == 12, sizeof_SkSFNTHeader_not_12);
+SK_COMPILE_ASSERT(sizeof(SkSFNTHeader::TableDirectoryEntry) == 16, sizeof_SkSFNTHeader_TableDirectoryEntry_not_16);
+
+#endif
diff --git a/src/utils/SkBitmapChecksummer.cpp b/src/utils/SkBitmapChecksummer.cpp
new file mode 100644
index 0000000000..bb9fc8d974
--- /dev/null
+++ b/src/utils/SkBitmapChecksummer.cpp
@@ -0,0 +1,69 @@
+
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBitmap.h"
+#include "SkBitmapChecksummer.h"
+#include "SkBitmapTransformer.h"
+#include "SkCityHash.h"
+#include "SkEndian.h"
+
+/**
+ * Write an integer value into a bytebuffer in little-endian order.
+ */
+static void write_int_to_buffer(int val, char* buf) {
+ val = SkEndian_SwapLE32(val);
+ for (int byte=0; byte<4; byte++) {
+ *buf++ = (char)(val & 0xff);
+ val = val >> 8;
+ }
+}
+
+/*static*/ uint64_t SkBitmapChecksummer::Compute64Internal(
+ const SkBitmap& bitmap, const SkBitmapTransformer& transformer) {
+ int pixelBufferSize = transformer.bytesNeededTotal();
+ int totalBufferSize = pixelBufferSize + 8; // leave room for x/y dimensions
+
+ SkAutoMalloc bufferManager(totalBufferSize);
+ char *bufferStart = static_cast<char *>(bufferManager.get());
+ char *bufPtr = bufferStart;
+ // start with the x/y dimensions
+ write_int_to_buffer(bitmap.width(), bufPtr);
+ bufPtr += 4;
+ write_int_to_buffer(bitmap.height(), bufPtr);
+ bufPtr += 4;
+
+ // add all the pixel data
+ if (!transformer.copyBitmapToPixelBuffer(bufPtr, pixelBufferSize)) {
+ return 0;
+ }
+ return SkCityHash::Compute64(bufferStart, totalBufferSize);
+}
+
+/*static*/ uint64_t SkBitmapChecksummer::Compute64(const SkBitmap& bitmap) {
+ const SkBitmapTransformer::PixelFormat kPixelFormat =
+ SkBitmapTransformer::kARGB_8888_Premul_PixelFormat;
+
+ // First, try to transform the existing bitmap.
+ const SkBitmapTransformer transformer =
+ SkBitmapTransformer(bitmap, kPixelFormat);
+ if (transformer.isValid(false)) {
+ return Compute64Internal(bitmap, transformer);
+ }
+
+ // Hmm, that didn't work. Maybe if we create a new
+ // kARGB_8888_Config version of the bitmap it will work better?
+ SkBitmap copyBitmap;
+ bitmap.copyTo(&copyBitmap, SkBitmap::kARGB_8888_Config);
+ const SkBitmapTransformer copyTransformer =
+ SkBitmapTransformer(copyBitmap, kPixelFormat);
+ if (copyTransformer.isValid(true)) {
+ return Compute64Internal(copyBitmap, copyTransformer);
+ } else {
+ return 0;
+ }
+}
diff --git a/src/utils/SkBitmapChecksummer.h b/src/utils/SkBitmapChecksummer.h
new file mode 100644
index 0000000000..63ac726c2e
--- /dev/null
+++ b/src/utils/SkBitmapChecksummer.h
@@ -0,0 +1,37 @@
+
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkBitmapChecksummer_DEFINED
+#define SkBitmapChecksummer_DEFINED
+
+#include "SkBitmap.h"
+#include "SkBitmapTransformer.h"
+
+/**
+ * Static class that can generate checksums from SkBitmaps.
+ */
+class SkBitmapChecksummer {
+public:
+ /**
+ * Returns a 64-bit checksum of the pixels in this bitmap.
+ *
+ * If this is unable to compute the checksum for some reason,
+ * it returns 0.
+ *
+ * Note: depending on the bitmap config, we may need to create an
+ * intermediate SkBitmap and copy the pixels over to it... so in some
+ * cases, performance and memory usage can suffer.
+ */
+ static uint64_t Compute64(const SkBitmap& bitmap);
+
+private:
+ static uint64_t Compute64Internal(const SkBitmap& bitmap,
+ const SkBitmapTransformer& transformer);
+};
+
+#endif
diff --git a/src/utils/SkBitmapTransformer.cpp b/src/utils/SkBitmapTransformer.cpp
new file mode 100644
index 0000000000..c8356d4238
--- /dev/null
+++ b/src/utils/SkBitmapTransformer.cpp
@@ -0,0 +1,87 @@
+
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBitmap.h"
+#include "SkBitmapTransformer.h"
+#include "SkColorPriv.h"
+#include "SkTypes.h"
+
+bool SkBitmapTransformer::isValid(bool logReason) const {
+ bool retval = true;
+
+ switch(fPixelFormat) {
+ case kARGB_8888_Premul_PixelFormat:
+ break;
+ default:
+ if (logReason) {
+ SkDEBUGF(("PixelFormat %d not supported\n", fPixelFormat));
+ }
+ retval = false;
+ }
+
+ SkBitmap::Config bitmapConfig = fBitmap.config();
+ switch(bitmapConfig) {
+ case SkBitmap::kARGB_8888_Config:
+ break;
+ default:
+ if (logReason) {
+ SkDEBUGF(("SkBitmap::Config %d not supported\n", bitmapConfig));
+ }
+ retval = false;
+ }
+
+ return retval;
+}
+
+/**
+ * Transform from kARGB_8888_Config to kARGB_8888_Premul_PixelFormat.
+ *
+ * Similar to the various scanline transformers in
+ * src/images/transform_scanline.h .
+ */
+static void transform_scanline(const char* SK_RESTRICT src, int width,
+ char* SK_RESTRICT dst) {
+ const SkPMColor* SK_RESTRICT srcP = reinterpret_cast<const SkPMColor*>(src);
+ for (int i = 0; i < width; i++) {
+ SkPMColor c = *srcP++;
+ unsigned a = SkGetPackedA32(c);
+ unsigned r = SkGetPackedR32(c);
+ unsigned g = SkGetPackedG32(c);
+ unsigned b = SkGetPackedB32(c);
+ *dst++ = a;
+ *dst++ = r;
+ *dst++ = g;
+ *dst++ = b;
+ }
+}
+
+bool SkBitmapTransformer::copyBitmapToPixelBuffer(void *dstBuffer,
+ size_t dstBufferSize) const {
+ if (!this->isValid(true)) {
+ return false;
+ }
+ size_t bytesNeeded = this->bytesNeededTotal();
+ if (dstBufferSize < bytesNeeded) {
+ SkDEBUGF(("dstBufferSize %d must be >= %d\n", dstBufferSize, bytesNeeded));
+ return false;
+ }
+
+ fBitmap.lockPixels();
+ int width = fBitmap.width();
+ size_t srcRowBytes = fBitmap.rowBytes();
+ size_t dstRowBytes = this->bytesNeededPerRow();
+ const char *srcBytes = const_cast<const char *>(static_cast<char*>(fBitmap.getPixels()));
+ char *dstBytes = static_cast<char *>(dstBuffer);
+ for (int y = 0; y < fBitmap.height(); y++) {
+ transform_scanline(srcBytes, width, dstBytes);
+ srcBytes += srcRowBytes;
+ dstBytes += dstRowBytes;
+ }
+ fBitmap.unlockPixels();
+ return true;
+}
diff --git a/src/utils/SkBitmapTransformer.h b/src/utils/SkBitmapTransformer.h
new file mode 100644
index 0000000000..70971ac625
--- /dev/null
+++ b/src/utils/SkBitmapTransformer.h
@@ -0,0 +1,104 @@
+
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkBitmapTransformer_DEFINED
+#define SkBitmapTransformer_DEFINED
+
+#include "SkBitmap.h"
+
+/**
+ * Class that can copy pixel data out of an SkBitmap, transforming it
+ * into the appropriate PixelFormat.
+ *
+ * As noted in https://codereview.appspot.com/6849119/#msg6 and
+ * https://codereview.appspot.com/6900047 , at some point we might want
+ * to make this more general purpose:
+ * - support more PixelFormats
+ * - use existing SkCanvas::Config8888 enum instead of new PixelFormat enum
+ * - add method to copy pixel data for a single row, instead of the whole bitmap
+ * - add methods to copy pixel data INTO an SkBitmap
+ *
+ * That would allow us to replace SkCopyConfig8888ToBitmap() in
+ * src/core/SkConfig8888.h , as well as the transformations used by
+ * src/images/SkImageDecoder_libpng.cpp , with this common code.
+ *
+ * But for now, we want something more narrowly targeted, just
+ * supplying what is needed by SkBitmapChecksummer.
+ */
+class SkBitmapTransformer {
+public:
+ enum PixelFormat {
+ // 32 bits per pixel, ARGB byte order, with the alpha-channel
+ // value premultiplied into the R/G/B channel values.
+ kARGB_8888_Premul_PixelFormat,
+
+ // marks the end of the list
+ kLast_PixelFormat = kARGB_8888_Premul_PixelFormat,
+ };
+
+ /**
+ * Creates an SkBitmapTransformer instance that can transform between
+ * the given bitmap and a pixel buffer with given pixelFormat.
+ *
+ * Call IsValid() before using, to confirm that this particular
+ * bitmap/pixelFormat combination is supported!
+ */
+ SkBitmapTransformer(const SkBitmap& bitmap, PixelFormat pixelFormat) :
+ fBitmap(bitmap), fPixelFormat(pixelFormat) {}
+
+ /**
+ * Returns true iff we can convert between fBitmap and fPixelFormat.
+ * If this returns false, the return values of any other methods will
+ * be meaningless!
+ *
+ * @param logReason whether to log the reason why this combination
+ * is unsupported (only applies in debug mode)
+ */
+ bool isValid(bool logReason=false) const;
+
+ /**
+ * Returns the number of bytes needed to store a single row of the
+ * bitmap's pixels if converted to pixelFormat.
+ */
+ size_t bytesNeededPerRow() const {
+ // This is hard-coded for the single supported PixelFormat.
+ return fBitmap.width() * 4;
+ }
+
+ /**
+ * Returns the number of bytes needed to store the entire bitmap
+ * if converted to pixelFormat, ASSUMING that it is written
+ * out as a single contiguous blob of pixels (no leftover bytes
+ * at the end of each row).
+ */
+ size_t bytesNeededTotal() const {
+ return this->bytesNeededPerRow() * fBitmap.height();
+ }
+
+ /**
+ * Writes the entire bitmap into dstBuffer, using the already-specified
+ * pixelFormat. Returns true if successful.
+ *
+ * dstBufferSize is the maximum allowable bytes to write into dstBuffer;
+ * if that is not large enough to hold the entire bitmap, then this
+ * will fail immediately and return false.
+ * We force the caller to pass this in to avoid buffer overruns in
+ * unanticipated cases.
+ *
+ * All pixels for all rows will be written into dstBuffer as a
+ * single contiguous blob (no skipped pixels at the end of each
+ * row).
+ */
+ bool copyBitmapToPixelBuffer (void *dstBuffer, size_t dstBufferSize) const;
+
+private:
+ const SkBitmap& fBitmap;
+ const PixelFormat fPixelFormat;
+};
+
+#endif
diff --git a/src/utils/SkCityHash.cpp b/src/utils/SkCityHash.cpp
new file mode 100644
index 0000000000..a21aa89634
--- /dev/null
+++ b/src/utils/SkCityHash.cpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**
+ * Pass any calls through to the CityHash library.
+ * This is the only source file that accesses the CityHash code directly.
+ */
+
+#include "SkCityHash.h"
+#include "SkTypes.h"
+#include "city.h"
+
+uint32_t SkCityHash::Compute32(const char *data, size_t size) {
+ return CityHash32(data, size);
+}
+
+uint64_t SkCityHash::Compute64(const char *data, size_t size) {
+ return CityHash64(data, size);
+}
diff --git a/src/utils/SkCityHash.h b/src/utils/SkCityHash.h
new file mode 100644
index 0000000000..c69e0fb37b
--- /dev/null
+++ b/src/utils/SkCityHash.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**
+ * Hash functions, using the CityHash algorithm.
+ *
+ * Results are guaranteed to be:
+ * 1. consistent across revisions of the library (for a given set
+ * of bytes, the checksum generated at one revision of the Skia
+ * library will match the one generated on any other revision of
+ * the Skia library)
+ * 2. consistent across platforms (for a given
+ * set of bytes, the checksum generated on one platform will
+ * match the one generated on any other platform)
+ */
+
+#ifndef SkCityHash_DEFINED
+#define SkCityHash_DEFINED
+
+#include "SkTypes.h"
+
+class SkCityHash : SkNoncopyable {
+public:
+ /**
+ * Compute a 32-bit checksum for a given data block.
+ *
+ * @param data Memory address of the data block to be processed.
+ * @param size Size of the data block in bytes.
+ * @return checksum result
+ */
+ static uint32_t Compute32(const char *data, size_t size);
+
+ /**
+ * Compute a 64-bit checksum for a given data block.
+ *
+ * @param data Memory address of the data block to be processed.
+ * @param size Size of the data block in bytes.
+ * @return checksum result
+ */
+ static uint64_t Compute64(const char *data, size_t size);
+};
+
+#endif
diff --git a/src/utils/SkCondVar.cpp b/src/utils/SkCondVar.cpp
new file mode 100644
index 0000000000..5d001c0edd
--- /dev/null
+++ b/src/utils/SkCondVar.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkCondVar.h"
+
+SkCondVar::SkCondVar() {
+#ifdef SK_USE_POSIX_THREADS
+ pthread_mutex_init(&fMutex, NULL /* default mutex attr */);
+ pthread_cond_init(&fCond, NULL /* default cond attr */);
+#elif defined(SK_BUILD_FOR_WIN32)
+ InitializeCriticalSection(&fCriticalSection);
+ InitializeConditionVariable(&fCondition);
+#endif
+}
+
+SkCondVar::~SkCondVar() {
+#ifdef SK_USE_POSIX_THREADS
+ pthread_mutex_destroy(&fMutex);
+ pthread_cond_destroy(&fCond);
+#elif defined(SK_BUILD_FOR_WIN32)
+ DeleteCriticalSection(&fCriticalSection);
+ // No need to clean up fCondition.
+#endif
+}
+
+void SkCondVar::lock() {
+#ifdef SK_USE_POSIX_THREADS
+ pthread_mutex_lock(&fMutex);
+#elif defined(SK_BUILD_FOR_WIN32)
+ EnterCriticalSection(&fCriticalSection);
+#endif
+}
+
+void SkCondVar::unlock() {
+#ifdef SK_USE_POSIX_THREADS
+ pthread_mutex_unlock(&fMutex);
+#elif defined(SK_BUILD_FOR_WIN32)
+ LeaveCriticalSection(&fCriticalSection);
+#endif
+}
+
+void SkCondVar::wait() {
+#ifdef SK_USE_POSIX_THREADS
+ pthread_cond_wait(&fCond, &fMutex);
+#elif defined(SK_BUILD_FOR_WIN32)
+ SleepConditionVariableCS(&fCondition, &fCriticalSection, INFINITE);
+#endif
+}
+
+void SkCondVar::signal() {
+#ifdef SK_USE_POSIX_THREADS
+ pthread_cond_signal(&fCond);
+#elif defined(SK_BUILD_FOR_WIN32)
+ WakeConditionVariable(&fCondition);
+#endif
+}
+
+void SkCondVar::broadcast() {
+#ifdef SK_USE_POSIX_THREADS
+ pthread_cond_broadcast(&fCond);
+#elif defined(SK_BUILD_FOR_WIN32)
+ WakeAllConditionVariable(&fCondition);
+#endif
+}
diff --git a/src/utils/SkCountdown.cpp b/src/utils/SkCountdown.cpp
new file mode 100644
index 0000000000..5b476cc094
--- /dev/null
+++ b/src/utils/SkCountdown.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkCountdown.h"
+#include "SkThread.h"
+
+SkCountdown::SkCountdown(int32_t count)
+: fCount(count) {}
+
+void SkCountdown::reset(int32_t count) {
+ fCount = count;
+}
+
+void SkCountdown::run() {
+ if (sk_atomic_dec(&fCount) == 1) {
+ fReady.lock();
+ fReady.signal();
+ fReady.unlock();
+ }
+}
+
+void SkCountdown::wait() {
+ fReady.lock();
+ while (fCount > 0) {
+ fReady.wait();
+ }
+ fReady.unlock();
+}
+
diff --git a/src/utils/SkDeferredCanvas.cpp b/src/utils/SkDeferredCanvas.cpp
index 13b8d282c6..a1e32bc373 100644
--- a/src/utils/SkDeferredCanvas.cpp
+++ b/src/utils/SkDeferredCanvas.cpp
@@ -14,11 +14,13 @@
#include "SkDrawFilter.h"
#include "SkGPipe.h"
#include "SkPaint.h"
+#include "SkRRect.h"
#include "SkShader.h"
enum {
// Deferred canvas will auto-flush when recording reaches this limit
kDefaultMaxRecordingStorageBytes = 64*1024*1024,
+ kDeferredCanvasBitmapSizeThreshold = ~0, // Disables this feature
};
enum PlaybackMode {
@@ -27,8 +29,10 @@ enum PlaybackMode {
};
namespace {
-bool shouldDrawImmediately(const SkBitmap* bitmap, const SkPaint* paint) {
- if (bitmap && bitmap->getTexture() && !bitmap->isImmutable()) {
+bool shouldDrawImmediately(const SkBitmap* bitmap, const SkPaint* paint,
+ size_t bitmapSizeThreshold) {
+ if (bitmap && ((bitmap->getTexture() && !bitmap->isImmutable()) ||
+ (bitmap->getSize() > bitmapSizeThreshold))) {
return true;
}
if (paint) {
@@ -50,36 +54,6 @@ bool shouldDrawImmediately(const SkBitmap* bitmap, const SkPaint* paint) {
}
}
-class AutoImmediateDrawIfNeeded {
-public:
- AutoImmediateDrawIfNeeded(SkDeferredCanvas& canvas, const SkBitmap* bitmap,
- const SkPaint* paint) {
- this->init(canvas, bitmap, paint);
- }
-
- AutoImmediateDrawIfNeeded(SkDeferredCanvas& canvas, const SkPaint* paint) {
- this->init(canvas, NULL, paint);
- }
-
- ~AutoImmediateDrawIfNeeded() {
- if (fCanvas) {
- fCanvas->setDeferredDrawing(true);
- }
- }
-private:
- void init(SkDeferredCanvas& canvas, const SkBitmap* bitmap, const SkPaint* paint)
- {
- if (canvas.isDeferredDrawing() && shouldDrawImmediately(bitmap, paint)) {
- canvas.setDeferredDrawing(false);
- fCanvas = &canvas;
- } else {
- fCanvas = NULL;
- }
- }
-
- SkDeferredCanvas* fCanvas;
-};
-
namespace {
bool isPaintOpaque(const SkPaint* paint,
@@ -244,6 +218,8 @@ public:
bool hasPendingCommands();
size_t storageAllocatedForRecording() const;
size_t freeMemoryIfPossible(size_t bytesToFree);
+ size_t getBitmapSizeThreshold() const;
+ void setBitmapSizeThreshold(size_t sizeThreshold);
void flushPendingCommands(PlaybackMode);
void skipPendingCommands();
void setMaxRecordingStorage(size_t);
@@ -339,6 +315,7 @@ private:
bool fFreshFrame;
size_t fMaxRecordingStorageBytes;
size_t fPreviousStorageAllocated;
+ size_t fBitmapSizeThreshold;
};
DeferredDevice::DeferredDevice(
@@ -347,7 +324,8 @@ DeferredDevice::DeferredDevice(
immediateDevice->height(), immediateDevice->isOpaque())
, fRecordingCanvas(NULL)
, fFreshFrame(true)
- , fPreviousStorageAllocated(0){
+ , fPreviousStorageAllocated(0)
+ , fBitmapSizeThreshold(kDeferredCanvasBitmapSizeThreshold){
fMaxRecordingStorageBytes = kDefaultMaxRecordingStorageBytes;
fNotificationClient = notificationClient;
@@ -406,7 +384,7 @@ void DeferredDevice::flushPendingCommands(PlaybackMode playbackMode) {
fNotificationClient->prepareForDraw();
}
fPipeWriter.flushRecording(true);
- fPipeController.playback(playbackMode);
+ fPipeController.playback(kSilent_PlaybackMode == playbackMode);
if (playbackMode == kNormal_PlaybackMode && fNotificationClient) {
fNotificationClient->flushedDrawCommands();
}
@@ -424,6 +402,14 @@ size_t DeferredDevice::freeMemoryIfPossible(size_t bytesToFree) {
return val;
}
+size_t DeferredDevice::getBitmapSizeThreshold() const {
+ return fBitmapSizeThreshold;
+}
+
+void DeferredDevice::setBitmapSizeThreshold(size_t sizeThreshold) {
+ fBitmapSizeThreshold = sizeThreshold;
+}
+
size_t DeferredDevice::storageAllocatedForRecording() const {
return (fPipeController.storageAllocatedForRecording()
+ fPipeWriter.storageAllocatedForRecording());
@@ -492,7 +478,7 @@ void DeferredDevice::writePixels(const SkBitmap& bitmap,
SkPaint paint;
paint.setXfermodeMode(SkXfermode::kSrc_Mode);
- if (shouldDrawImmediately(&bitmap, NULL)) {
+ if (shouldDrawImmediately(&bitmap, NULL, getBitmapSizeThreshold())) {
this->flushPendingCommands(kNormal_PlaybackMode);
fImmediateCanvas->drawSprite(bitmap, x, y, &paint);
} else {
@@ -527,6 +513,37 @@ bool DeferredDevice::onReadPixels(
x, y, config8888);
}
+class AutoImmediateDrawIfNeeded {
+public:
+ AutoImmediateDrawIfNeeded(SkDeferredCanvas& canvas, const SkBitmap* bitmap,
+ const SkPaint* paint) {
+ this->init(canvas, bitmap, paint);
+ }
+
+ AutoImmediateDrawIfNeeded(SkDeferredCanvas& canvas, const SkPaint* paint) {
+ this->init(canvas, NULL, paint);
+ }
+
+ ~AutoImmediateDrawIfNeeded() {
+ if (fCanvas) {
+ fCanvas->setDeferredDrawing(true);
+ }
+ }
+private:
+ void init(SkDeferredCanvas& canvas, const SkBitmap* bitmap, const SkPaint* paint)
+ {
+ DeferredDevice* device = static_cast<DeferredDevice*>(canvas.getDevice());
+ if (canvas.isDeferredDrawing() && (NULL != device) &&
+ shouldDrawImmediately(bitmap, paint, device->getBitmapSizeThreshold())) {
+ canvas.setDeferredDrawing(false);
+ fCanvas = &canvas;
+ } else {
+ fCanvas = NULL;
+ }
+ }
+
+ SkDeferredCanvas* fCanvas;
+};
SkDeferredCanvas::SkDeferredCanvas() {
this->init();
@@ -554,6 +571,12 @@ size_t SkDeferredCanvas::freeMemoryIfPossible(size_t bytesToFree) {
return this->getDeferredDevice()->freeMemoryIfPossible(bytesToFree);
}
+void SkDeferredCanvas::setBitmapSizeThreshold(size_t sizeThreshold) {
+ DeferredDevice* deferredDevice = this->getDeferredDevice();
+ SkASSERT(deferredDevice);
+ deferredDevice->setBitmapSizeThreshold(sizeThreshold);
+}
+
void SkDeferredCanvas::recordedDrawCommand() {
if (fDeferredDrawing) {
this->getDeferredDevice()->recordedDrawCommand();
@@ -661,25 +684,8 @@ bool SkDeferredCanvas::isFullFrame(const SkRect* rect,
}
}
- switch (canvas->getClipType()) {
- case SkCanvas::kRect_ClipType :
- {
- SkIRect bounds;
- canvas->getClipDeviceBounds(&bounds);
- if (bounds.fLeft > 0 || bounds.fTop > 0 ||
- bounds.fRight < canvasSize.fWidth ||
- bounds.fBottom < canvasSize.fHeight)
- return false;
- }
- break;
- case SkCanvas::kComplex_ClipType :
- return false; // conservative
- case SkCanvas::kEmpty_ClipType:
- default:
- break;
- };
-
- return true;
+ return this->getClipStack()->quickContains(SkRect::MakeXYWH(0, 0,
+ SkIntToScalar(canvasSize.fWidth), SkIntToScalar(canvasSize.fHeight)));
}
int SkDeferredCanvas::save(SaveFlags flags) {
@@ -760,6 +766,15 @@ bool SkDeferredCanvas::clipRect(const SkRect& rect,
return val;
}
+bool SkDeferredCanvas::clipRRect(const SkRRect& rrect,
+ SkRegion::Op op,
+ bool doAntiAlias) {
+ this->drawingCanvas()->clipRRect(rrect, op, doAntiAlias);
+ bool val = this->INHERITED::clipRRect(rrect, op, doAntiAlias);
+ this->recordedDrawCommand();
+ return val;
+}
+
bool SkDeferredCanvas::clipPath(const SkPath& path,
SkRegion::Op op,
bool doAntiAlias) {
@@ -804,6 +819,12 @@ void SkDeferredCanvas::drawPoints(PointMode mode, size_t count,
this->recordedDrawCommand();
}
+void SkDeferredCanvas::drawOval(const SkRect& rect, const SkPaint& paint) {
+ AutoImmediateDrawIfNeeded autoDraw(*this, &paint);
+ this->drawingCanvas()->drawOval(rect, paint);
+ this->recordedDrawCommand();
+}
+
void SkDeferredCanvas::drawRect(const SkRect& rect, const SkPaint& paint) {
if (fDeferredDrawing && this->isFullFrame(&rect, &paint) &&
isPaintOpaque(&paint)) {
@@ -815,6 +836,18 @@ void SkDeferredCanvas::drawRect(const SkRect& rect, const SkPaint& paint) {
this->recordedDrawCommand();
}
+void SkDeferredCanvas::drawRRect(const SkRRect& rrect, const SkPaint& paint) {
+ if (rrect.isRect()) {
+ this->SkDeferredCanvas::drawRect(rrect.getBounds(), paint);
+ } else if (rrect.isOval()) {
+ this->SkDeferredCanvas::drawOval(rrect.getBounds(), paint);
+ } else {
+ AutoImmediateDrawIfNeeded autoDraw(*this, &paint);
+ this->drawingCanvas()->drawRRect(rrect, paint);
+ this->recordedDrawCommand();
+ }
+}
+
void SkDeferredCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
AutoImmediateDrawIfNeeded autoDraw(*this, &paint);
this->drawingCanvas()->drawPath(path, paint);
diff --git a/src/utils/SkDumpCanvas.cpp b/src/utils/SkDumpCanvas.cpp
index 9135849208..2722fb4c04 100644
--- a/src/utils/SkDumpCanvas.cpp
+++ b/src/utils/SkDumpCanvas.cpp
@@ -8,6 +8,7 @@
#include "SkDumpCanvas.h"
#include "SkPicture.h"
#include "SkPixelRef.h"
+#include "SkRRect.h"
#include "SkString.h"
#include <stdarg.h>
@@ -31,6 +32,31 @@ static void toString(const SkIRect& r, SkString* str) {
str->appendf("[%d,%d %d:%d]", r.fLeft, r.fTop, r.width(), r.height());
}
+static void toString(const SkRRect& rrect, SkString* str) {
+ SkRect r = rrect.getBounds();
+ str->appendf("[%g,%g %g:%g]",
+ SkScalarToFloat(r.fLeft), SkScalarToFloat(r.fTop),
+ SkScalarToFloat(r.width()), SkScalarToFloat(r.height()));
+ if (rrect.isOval()) {
+ str->append("()");
+ } else if (rrect.isSimple()) {
+ const SkVector& rad = rrect.getSimpleRadii();
+ str->appendf("(%g,%g)", rad.x(), rad.y());
+ } else if (rrect.isComplex()) {
+ SkVector radii[4] = {
+ rrect.radii(SkRRect::kUpperLeft_Corner),
+ rrect.radii(SkRRect::kUpperRight_Corner),
+ rrect.radii(SkRRect::kLowerRight_Corner),
+ rrect.radii(SkRRect::kLowerLeft_Corner),
+ };
+ str->appendf("(%g,%g %g,%g %g,%g %g,%g)",
+ radii[0].x(), radii[0].y(),
+ radii[1].x(), radii[1].y(),
+ radii[2].x(), radii[2].y(),
+ radii[3].x(), radii[3].y());
+ }
+}
+
static void dumpVerbs(const SkPath& path, SkString* str) {
SkPath::Iter iter(path, false);
SkPoint pts[4];
@@ -273,6 +299,14 @@ bool SkDumpCanvas::clipRect(const SkRect& rect, SkRegion::Op op, bool doAA) {
return this->INHERITED::clipRect(rect, op, doAA);
}
+bool SkDumpCanvas::clipRRect(const SkRRect& rrect, SkRegion::Op op, bool doAA) {
+ SkString str;
+ toString(rrect, &str);
+ this->dump(kClip_Verb, NULL, "clipRRect(%s %s %s)", str.c_str(), toString(op),
+ bool_to_aastring(doAA));
+ return this->INHERITED::clipRRect(rrect, op, doAA);
+}
+
bool SkDumpCanvas::clipPath(const SkPath& path, SkRegion::Op op, bool doAA) {
SkString str;
toString(path, &str);
@@ -301,12 +335,24 @@ void SkDumpCanvas::drawPoints(PointMode mode, size_t count,
count);
}
+void SkDumpCanvas::drawOval(const SkRect& rect, const SkPaint& paint) {
+ SkString str;
+ toString(rect, &str);
+ this->dump(kDrawOval_Verb, &paint, "drawOval(%s)", str.c_str());
+}
+
void SkDumpCanvas::drawRect(const SkRect& rect, const SkPaint& paint) {
SkString str;
toString(rect, &str);
this->dump(kDrawRect_Verb, &paint, "drawRect(%s)", str.c_str());
}
+void SkDumpCanvas::drawRRect(const SkRRect& rrect, const SkPaint& paint) {
+ SkString str;
+ toString(rrect, &str);
+ this->dump(kDrawRRect_Verb, &paint, "drawRRect(%s)", str.c_str());
+}
+
void SkDumpCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
SkString str;
toString(path, &str);
diff --git a/src/utils/SkMatrix44.cpp b/src/utils/SkMatrix44.cpp
index f00e399191..e899cf81e3 100644
--- a/src/utils/SkMatrix44.cpp
+++ b/src/utils/SkMatrix44.cpp
@@ -10,10 +10,6 @@
#include "SkMatrix44.h"
-SkMatrix44::SkMatrix44() {
- this->setIdentity();
-}
-
SkMatrix44::SkMatrix44(const SkMatrix44& src) {
memcpy(this, &src, sizeof(src));
}
@@ -22,16 +18,69 @@ SkMatrix44::SkMatrix44(const SkMatrix44& a, const SkMatrix44& b) {
this->setConcat(a, b);
}
-SkMScalar SkMatrix44::get(int row, int col) const {
- SkASSERT(row <= 3 && row >= 0);
- SkASSERT(col <= 3 && col >= 0);
- return fMat[col][row];
+static inline bool eq4(const SkMScalar* SK_RESTRICT a,
+ const SkMScalar* SK_RESTRICT b) {
+ return (a[0] == b[0]) & (a[1] == b[1]) & (a[2] == b[2]) & (a[3] == b[3]);
+}
+
+bool SkMatrix44::operator==(const SkMatrix44& other) const {
+ if (this == &other) {
+ return true;
+ }
+
+ if (this->isTriviallyIdentity() && other.isTriviallyIdentity()) {
+ return true;
+ }
+
+ const SkMScalar* SK_RESTRICT a = &fMat[0][0];
+ const SkMScalar* SK_RESTRICT b = &other.fMat[0][0];
+
+#if 0
+ for (int i = 0; i < 16; ++i) {
+ if (a[i] != b[i]) {
+ return false;
+ }
+ }
+ return true;
+#else
+ // to reduce branch instructions, we compare 4 at a time.
+ // see bench/Matrix44Bench.cpp for test.
+ if (!eq4(&a[0], &b[0])) {
+ return false;
+ }
+ if (!eq4(&a[4], &b[4])) {
+ return false;
+ }
+ if (!eq4(&a[8], &b[8])) {
+ return false;
+ }
+ return eq4(&a[12], &b[12]);
+#endif
}
-void SkMatrix44::set(int row, int col, const SkMScalar& value) {
- SkASSERT(row <= 3 && row >= 0);
- SkASSERT(col <= 3 && col >= 0);
- fMat[col][row] = value;
+///////////////////////////////////////////////////////////////////////////////
+
+int SkMatrix44::computeTypeMask() const {
+ unsigned mask = 0;
+
+ if (0 != perspX() || 0 != perspY() || 0 != perspZ() || 1 != fMat[3][3]) {
+ return kTranslate_Mask | kScale_Mask | kAffine_Mask | kPerspective_Mask;
+ }
+
+ if (0 != transX() || 0 != transY() || 0 != transZ()) {
+ mask |= kTranslate_Mask;
+ }
+
+ if (1 != scaleX() || 1 != scaleY() || 1 != scaleZ()) {
+ mask |= kScale_Mask;
+ }
+
+ if (0 != fMat[1][0] || 0 != fMat[0][1] || 0 != fMat[0][2] ||
+ 0 != fMat[2][0] || 0 != fMat[1][2] || 0 != fMat[2][1]) {
+ mask |= kAffine_Mask;
+ }
+
+ return mask;
}
///////////////////////////////////////////////////////////////////////////////
@@ -42,7 +91,7 @@ void SkMatrix44::asColMajorf(float dst[]) const {
for (int i = 0; i < 16; ++i) {
dst[i] = SkMScalarToFloat(src[i]);
}
-#else
+#elif defined SK_MSCALAR_IS_FLOAT
memcpy(dst, src, 16 * sizeof(float));
#endif
}
@@ -51,7 +100,7 @@ void SkMatrix44::asColMajord(double dst[]) const {
const SkMScalar* src = &fMat[0][0];
#ifdef SK_MSCALAR_IS_DOUBLE
memcpy(dst, src, 16 * sizeof(double));
-#else
+#elif defined SK_MSCALAR_IS_FLOAT
for (int i = 0; i < 16; ++i) {
dst[i] = SkMScalarToDouble(src[i]);
}
@@ -82,88 +131,183 @@ void SkMatrix44::asRowMajord(double dst[]) const {
}
}
-///////////////////////////////////////////////////////////////////////////////
+void SkMatrix44::setColMajorf(const float src[]) {
+ SkMScalar* dst = &fMat[0][0];
+#ifdef SK_MSCALAR_IS_DOUBLE
+ for (int i = 0; i < 16; ++i) {
+ dst[i] = SkMScalarToFloat(src[i]);
+ }
+#elif defined SK_MSCALAR_IS_FLOAT
+ memcpy(dst, src, 16 * sizeof(float));
+#endif
-bool SkMatrix44::isIdentity() const {
- static const SkMScalar sIdentityMat[4][4] = {
- { 1, 0, 0, 0 },
- { 0, 1, 0, 0 },
- { 0, 0, 1, 0 },
- { 0, 0, 0, 1 },
- };
- return !memcmp(fMat, sIdentityMat, sizeof(fMat));
+ this->dirtyTypeMask();
+}
+
+void SkMatrix44::setColMajord(const double src[]) {
+ SkMScalar* dst = &fMat[0][0];
+#ifdef SK_MSCALAR_IS_DOUBLE
+ memcpy(dst, src, 16 * sizeof(double));
+#elif defined SK_MSCALAR_IS_FLOAT
+ for (int i = 0; i < 16; ++i) {
+ dst[i] = SkDoubleToMScalar(src[i]);
+ }
+#endif
+
+ this->dirtyTypeMask();
+}
+
+void SkMatrix44::setRowMajorf(const float src[]) {
+ SkMScalar* dst = &fMat[0][0];
+ for (int i = 0; i < 4; ++i) {
+ dst[0] = SkMScalarToFloat(src[0]);
+ dst[4] = SkMScalarToFloat(src[1]);
+ dst[8] = SkMScalarToFloat(src[2]);
+ dst[12] = SkMScalarToFloat(src[3]);
+ src += 4;
+ dst += 1;
+ }
+ this->dirtyTypeMask();
+}
+
+void SkMatrix44::setRowMajord(const double src[]) {
+ SkMScalar* dst = &fMat[0][0];
+ for (int i = 0; i < 4; ++i) {
+ dst[0] = SkDoubleToMScalar(src[0]);
+ dst[4] = SkDoubleToMScalar(src[1]);
+ dst[8] = SkDoubleToMScalar(src[2]);
+ dst[12] = SkDoubleToMScalar(src[3]);
+ src += 4;
+ dst += 1;
+ }
+ this->dirtyTypeMask();
}
///////////////////////////////////////////////////////////////////////////////
+const SkMatrix44& SkMatrix44::I() {
+ static const SkMatrix44 gIdentity44(kIdentity_Constructor);
+ return gIdentity44;
+}
+
void SkMatrix44::setIdentity() {
sk_bzero(fMat, sizeof(fMat));
fMat[0][0] = fMat[1][1] = fMat[2][2] = fMat[3][3] = 1;
+ this->setTypeMask(kIdentity_Mask);
}
void SkMatrix44::set3x3(SkMScalar m00, SkMScalar m01, SkMScalar m02,
SkMScalar m10, SkMScalar m11, SkMScalar m12,
SkMScalar m20, SkMScalar m21, SkMScalar m22) {
- sk_bzero(fMat, sizeof(fMat));
fMat[0][0] = m00; fMat[0][1] = m01; fMat[0][2] = m02; fMat[0][3] = 0;
fMat[1][0] = m10; fMat[1][1] = m11; fMat[1][2] = m12; fMat[1][3] = 0;
fMat[2][0] = m20; fMat[2][1] = m21; fMat[2][2] = m22; fMat[2][3] = 0;
fMat[3][0] = 0; fMat[3][1] = 0; fMat[3][2] = 0; fMat[3][3] = 1;
+ this->dirtyTypeMask();
}
///////////////////////////////////////////////////////////////////////////////
-void SkMatrix44::setTranslate(SkMScalar tx, SkMScalar ty, SkMScalar tz) {
+void SkMatrix44::setTranslate(SkMScalar dx, SkMScalar dy, SkMScalar dz) {
this->setIdentity();
- fMat[3][0] = tx;
- fMat[3][1] = ty;
- fMat[3][2] = tz;
- fMat[3][3] = 1;
+
+ if (!dx && !dy && !dz) {
+ return;
+ }
+
+ fMat[3][0] = dx;
+ fMat[3][1] = dy;
+ fMat[3][2] = dz;
+ this->setTypeMask(kTranslate_Mask);
}
void SkMatrix44::preTranslate(SkMScalar dx, SkMScalar dy, SkMScalar dz) {
- SkMatrix44 mat;
- mat.setTranslate(dx, dy, dz);
- this->preConcat(mat);
+ if (!dx && !dy && !dz) {
+ return;
+ }
+
+ const double X = SkMScalarToDouble(dx);
+ const double Y = SkMScalarToDouble(dy);
+ const double Z = SkMScalarToDouble(dz);
+
+ double tmp;
+ for (int i = 0; i < 4; ++i) {
+ tmp = fMat[0][i] * X + fMat[1][i] * Y + fMat[2][i] * Z + fMat[3][i];
+ fMat[3][i] = SkDoubleToMScalar(tmp);
+ }
+ this->dirtyTypeMask();
}
void SkMatrix44::postTranslate(SkMScalar dx, SkMScalar dy, SkMScalar dz) {
- fMat[3][0] += dx;
- fMat[3][1] += dy;
- fMat[3][2] += dz;
+ if (!dx && !dy && !dz) {
+ return;
+ }
+
+ if (this->getType() & kPerspective_Mask) {
+ for (int i = 0; i < 4; ++i) {
+ fMat[i][0] += fMat[i][3] * dx;
+ fMat[i][1] += fMat[i][3] * dy;
+ fMat[i][2] += fMat[i][3] * dz;
+ }
+ } else {
+ fMat[3][0] += dx;
+ fMat[3][1] += dy;
+ fMat[3][2] += dz;
+ this->dirtyTypeMask();
+ }
}
///////////////////////////////////////////////////////////////////////////////
void SkMatrix44::setScale(SkMScalar sx, SkMScalar sy, SkMScalar sz) {
- sk_bzero(fMat, sizeof(fMat));
+ this->setIdentity();
+
+ if (1 == sx && 1 == sy && 1 == sz) {
+ return;
+ }
+
fMat[0][0] = sx;
fMat[1][1] = sy;
fMat[2][2] = sz;
- fMat[3][3] = 1;
+ this->setTypeMask(kScale_Mask);
}
void SkMatrix44::preScale(SkMScalar sx, SkMScalar sy, SkMScalar sz) {
- SkMatrix44 tmp;
- tmp.setScale(sx, sy, sz);
- this->preConcat(tmp);
+ if (1 == sx && 1 == sy && 1 == sz) {
+ return;
+ }
+
+ // The implementation matrix * pureScale can be shortcut
+ // by knowing that pureScale components effectively scale
+ // the columns of the original matrix.
+ for (int i = 0; i < 4; i++) {
+ fMat[0][i] *= sx;
+ fMat[1][i] *= sy;
+ fMat[2][i] *= sz;
+ }
+ this->dirtyTypeMask();
}
void SkMatrix44::postScale(SkMScalar sx, SkMScalar sy, SkMScalar sz) {
+ if (1 == sx && 1 == sy && 1 == sz) {
+ return;
+ }
+
for (int i = 0; i < 4; i++) {
fMat[i][0] *= sx;
fMat[i][1] *= sy;
fMat[i][2] *= sz;
}
+ this->dirtyTypeMask();
}
///////////////////////////////////////////////////////////////////////////////
void SkMatrix44::setRotateAbout(SkMScalar x, SkMScalar y, SkMScalar z,
SkMScalar radians) {
- double len2 = x * x + y * y + z * z;
- if (len2 != 1) {
- if (len2 == 0) {
+ double len2 = (double)x * x + (double)y * y + (double)z * z;
+ if (1 != len2) {
+ if (0 == len2) {
this->setIdentity();
return;
}
@@ -206,18 +350,52 @@ void SkMatrix44::setRotateAboutUnit(SkMScalar x, SkMScalar y, SkMScalar z,
///////////////////////////////////////////////////////////////////////////////
+static bool bits_isonly(int value, int mask) {
+ return 0 == (value & ~mask);
+}
+
void SkMatrix44::setConcat(const SkMatrix44& a, const SkMatrix44& b) {
- SkMScalar result[4][4];
- for (int i = 0; i < 4; i++) {
+ const SkMatrix44::TypeMask a_mask = a.getType();
+ const SkMatrix44::TypeMask b_mask = b.getType();
+
+ if (kIdentity_Mask == a_mask) {
+ *this = b;
+ return;
+ }
+ if (kIdentity_Mask == b_mask) {
+ *this = a;
+ return;
+ }
+
+ bool useStorage = (this == &a || this == &b);
+ SkMScalar storage[16];
+ SkMScalar* result = useStorage ? storage : &fMat[0][0];
+
+ if (bits_isonly(a_mask | b_mask, kScale_Mask | kTranslate_Mask)) {
+ sk_bzero(result, sizeof(storage));
+ result[0] = a.fMat[0][0] * b.fMat[0][0];
+ result[5] = a.fMat[1][1] * b.fMat[1][1];
+ result[10] = a.fMat[2][2] * b.fMat[2][2];
+ result[12] = a.fMat[0][0] * b.fMat[3][0] + a.fMat[3][0];
+ result[13] = a.fMat[1][1] * b.fMat[3][1] + a.fMat[3][1];
+ result[14] = a.fMat[2][2] * b.fMat[3][2] + a.fMat[3][2];
+ result[15] = 1;
+ } else {
for (int j = 0; j < 4; j++) {
- double value = 0;
- for (int k = 0; k < 4; k++) {
- value += SkMScalarToDouble(a.fMat[k][i]) * b.fMat[j][k];
+ for (int i = 0; i < 4; i++) {
+ double value = 0;
+ for (int k = 0; k < 4; k++) {
+ value += SkMScalarToDouble(a.fMat[k][i]) * b.fMat[j][k];
+ }
+ *result++ = SkDoubleToMScalar(value);
}
- result[j][i] = SkDoubleToMScalar(value);
}
}
- memcpy(fMat, result, sizeof(result));
+
+ if (useStorage) {
+ memcpy(fMat, storage, sizeof(storage));
+ }
+ this->dirtyTypeMask();
}
///////////////////////////////////////////////////////////////////////////////
@@ -239,18 +417,45 @@ static inline double det3x3(double m00, double m01, double m02,
promoting our SkMScalar values to double (if needed).
*/
double SkMatrix44::determinant() const {
- return fMat[0][0] * det3x3(fMat[1][1], fMat[1][2], fMat[1][3],
- fMat[2][1], fMat[2][2], fMat[2][3],
- fMat[3][1], fMat[3][2], fMat[3][3]) -
- fMat[1][0] * det3x3(fMat[0][1], fMat[0][2], fMat[0][3],
- fMat[2][1], fMat[2][2], fMat[2][3],
- fMat[3][1], fMat[3][2], fMat[3][3]) +
- fMat[2][0] * det3x3(fMat[0][1], fMat[0][2], fMat[0][3],
- fMat[1][1], fMat[1][2], fMat[1][3],
- fMat[3][1], fMat[3][2], fMat[3][3]) -
- fMat[3][0] * det3x3(fMat[0][1], fMat[0][2], fMat[0][3],
- fMat[1][1], fMat[1][2], fMat[1][3],
- fMat[2][1], fMat[2][2], fMat[2][3]);
+ if (this->isIdentity()) {
+ return 1;
+ }
+ if (this->isScaleTranslate()) {
+ return fMat[0][0] * fMat[1][1] * fMat[2][2] * fMat[3][3];
+ }
+
+ double a00 = fMat[0][0];
+ double a01 = fMat[0][1];
+ double a02 = fMat[0][2];
+ double a03 = fMat[0][3];
+ double a10 = fMat[1][0];
+ double a11 = fMat[1][1];
+ double a12 = fMat[1][2];
+ double a13 = fMat[1][3];
+ double a20 = fMat[2][0];
+ double a21 = fMat[2][1];
+ double a22 = fMat[2][2];
+ double a23 = fMat[2][3];
+ double a30 = fMat[3][0];
+ double a31 = fMat[3][1];
+ double a32 = fMat[3][2];
+ double a33 = fMat[3][3];
+
+ double b00 = a00 * a11 - a01 * a10;
+ double b01 = a00 * a12 - a02 * a10;
+ double b02 = a00 * a13 - a03 * a10;
+ double b03 = a01 * a12 - a02 * a11;
+ double b04 = a01 * a13 - a03 * a11;
+ double b05 = a02 * a13 - a03 * a12;
+ double b06 = a20 * a31 - a21 * a30;
+ double b07 = a20 * a32 - a22 * a30;
+ double b08 = a20 * a33 - a23 * a30;
+ double b09 = a21 * a32 - a22 * a31;
+ double b10 = a21 * a33 - a23 * a31;
+ double b11 = a22 * a33 - a23 * a32;
+
+ // Calculate the determinant
+ return b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
}
///////////////////////////////////////////////////////////////////////////////
@@ -266,73 +471,327 @@ static inline double dabs(double x) {
}
bool SkMatrix44::invert(SkMatrix44* inverse) const {
- double det = this->determinant();
+ if (this->isIdentity()) {
+ if (inverse) {
+ *inverse = *this;
+ return true;
+ }
+ }
+ if (this->isTranslate()) {
+ if (inverse) {
+ inverse->setTranslate(-fMat[3][0], -fMat[3][1], -fMat[3][2]);
+ }
+ return true;
+ }
+ if (this->isScaleTranslate()) {
+ if (0 == fMat[0][0] * fMat[1][1] * fMat[2][2]) {
+ return false;
+ }
+ if (inverse) {
+ sk_bzero(inverse->fMat, sizeof(inverse->fMat));
+
+ inverse->fMat[3][0] = -fMat[3][0] / fMat[0][0];
+ inverse->fMat[3][1] = -fMat[3][1] / fMat[1][1];
+ inverse->fMat[3][2] = -fMat[3][2] / fMat[2][2];
+
+ inverse->fMat[0][0] = 1 / fMat[0][0];
+ inverse->fMat[1][1] = 1 / fMat[1][1];
+ inverse->fMat[2][2] = 1 / fMat[2][2];
+ inverse->fMat[3][3] = 1;
+
+ inverse->setTypeMask(this->getType());
+ }
+ return true;
+ }
+
+ double a00 = fMat[0][0];
+ double a01 = fMat[0][1];
+ double a02 = fMat[0][2];
+ double a03 = fMat[0][3];
+ double a10 = fMat[1][0];
+ double a11 = fMat[1][1];
+ double a12 = fMat[1][2];
+ double a13 = fMat[1][3];
+ double a20 = fMat[2][0];
+ double a21 = fMat[2][1];
+ double a22 = fMat[2][2];
+ double a23 = fMat[2][3];
+ double a30 = fMat[3][0];
+ double a31 = fMat[3][1];
+ double a32 = fMat[3][2];
+ double a33 = fMat[3][3];
+
+ double b00 = a00 * a11 - a01 * a10;
+ double b01 = a00 * a12 - a02 * a10;
+ double b02 = a00 * a13 - a03 * a10;
+ double b03 = a01 * a12 - a02 * a11;
+ double b04 = a01 * a13 - a03 * a11;
+ double b05 = a02 * a13 - a03 * a12;
+ double b06 = a20 * a31 - a21 * a30;
+ double b07 = a20 * a32 - a22 * a30;
+ double b08 = a20 * a33 - a23 * a30;
+ double b09 = a21 * a32 - a22 * a31;
+ double b10 = a21 * a33 - a23 * a31;
+ double b11 = a22 * a33 - a23 * a32;
+
+ // Calculate the determinant
+ double det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
+
if (dabs(det) < TOO_SMALL_FOR_DETERMINANT) {
return false;
}
if (NULL == inverse) {
return true;
}
+ double invdet = 1.0 / det;
+
+ b00 *= invdet;
+ b01 *= invdet;
+ b02 *= invdet;
+ b03 *= invdet;
+ b04 *= invdet;
+ b05 *= invdet;
+ b06 *= invdet;
+ b07 *= invdet;
+ b08 *= invdet;
+ b09 *= invdet;
+ b10 *= invdet;
+ b11 *= invdet;
+
+ inverse->fMat[0][0] = SkDoubleToMScalar(a11 * b11 - a12 * b10 + a13 * b09);
+ inverse->fMat[0][1] = SkDoubleToMScalar(a02 * b10 - a01 * b11 - a03 * b09);
+ inverse->fMat[0][2] = SkDoubleToMScalar(a31 * b05 - a32 * b04 + a33 * b03);
+ inverse->fMat[0][3] = SkDoubleToMScalar(a22 * b04 - a21 * b05 - a23 * b03);
+ inverse->fMat[1][0] = SkDoubleToMScalar(a12 * b08 - a10 * b11 - a13 * b07);
+ inverse->fMat[1][1] = SkDoubleToMScalar(a00 * b11 - a02 * b08 + a03 * b07);
+ inverse->fMat[1][2] = SkDoubleToMScalar(a32 * b02 - a30 * b05 - a33 * b01);
+ inverse->fMat[1][3] = SkDoubleToMScalar(a20 * b05 - a22 * b02 + a23 * b01);
+ inverse->fMat[2][0] = SkDoubleToMScalar(a10 * b10 - a11 * b08 + a13 * b06);
+ inverse->fMat[2][1] = SkDoubleToMScalar(a01 * b08 - a00 * b10 - a03 * b06);
+ inverse->fMat[2][2] = SkDoubleToMScalar(a30 * b04 - a31 * b02 + a33 * b00);
+ inverse->fMat[2][3] = SkDoubleToMScalar(a21 * b02 - a20 * b04 - a23 * b00);
+ inverse->fMat[3][0] = SkDoubleToMScalar(a11 * b07 - a10 * b09 - a12 * b06);
+ inverse->fMat[3][1] = SkDoubleToMScalar(a00 * b09 - a01 * b07 + a02 * b06);
+ inverse->fMat[3][2] = SkDoubleToMScalar(a31 * b01 - a30 * b03 - a32 * b00);
+ inverse->fMat[3][3] = SkDoubleToMScalar(a20 * b03 - a21 * b01 + a22 * b00);
+ inverse->dirtyTypeMask();
+
+ inverse->dirtyTypeMask();
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkMatrix44::transpose() {
+ SkTSwap(fMat[0][1], fMat[1][0]);
+ SkTSwap(fMat[0][2], fMat[2][0]);
+ SkTSwap(fMat[0][3], fMat[3][0]);
+ SkTSwap(fMat[1][2], fMat[2][1]);
+ SkTSwap(fMat[1][3], fMat[3][1]);
+ SkTSwap(fMat[2][3], fMat[3][2]);
+
+ if (!this->isTriviallyIdentity()) {
+ this->dirtyTypeMask();
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkMatrix44::mapScalars(const SkScalar src[4], SkScalar dst[4]) const {
+ SkScalar storage[4];
+ SkScalar* result = (src == dst) ? storage : dst;
- // we explicitly promote to doubles to keep the intermediate values in
- // higher precision (assuming SkMScalar isn't already a double)
- double m00 = fMat[0][0];
- double m01 = fMat[0][1];
- double m02 = fMat[0][2];
- double m03 = fMat[0][3];
- double m10 = fMat[1][0];
- double m11 = fMat[1][1];
- double m12 = fMat[1][2];
- double m13 = fMat[1][3];
- double m20 = fMat[2][0];
- double m21 = fMat[2][1];
- double m22 = fMat[2][2];
- double m23 = fMat[2][3];
- double m30 = fMat[3][0];
- double m31 = fMat[3][1];
- double m32 = fMat[3][2];
- double m33 = fMat[3][3];
-
- double tmp[4][4];
-
- tmp[0][0] = m12*m23*m31 - m13*m22*m31 + m13*m21*m32 - m11*m23*m32 - m12*m21*m33 + m11*m22*m33;
- tmp[0][1] = m03*m22*m31 - m02*m23*m31 - m03*m21*m32 + m01*m23*m32 + m02*m21*m33 - m01*m22*m33;
- tmp[0][2] = m02*m13*m31 - m03*m12*m31 + m03*m11*m32 - m01*m13*m32 - m02*m11*m33 + m01*m12*m33;
- tmp[0][3] = m03*m12*m21 - m02*m13*m21 - m03*m11*m22 + m01*m13*m22 + m02*m11*m23 - m01*m12*m23;
- tmp[1][0] = m13*m22*m30 - m12*m23*m30 - m13*m20*m32 + m10*m23*m32 + m12*m20*m33 - m10*m22*m33;
- tmp[1][1] = m02*m23*m30 - m03*m22*m30 + m03*m20*m32 - m00*m23*m32 - m02*m20*m33 + m00*m22*m33;
- tmp[1][2] = m03*m12*m30 - m02*m13*m30 - m03*m10*m32 + m00*m13*m32 + m02*m10*m33 - m00*m12*m33;
- tmp[1][3] = m02*m13*m20 - m03*m12*m20 + m03*m10*m22 - m00*m13*m22 - m02*m10*m23 + m00*m12*m23;
- tmp[2][0] = m11*m23*m30 - m13*m21*m30 + m13*m20*m31 - m10*m23*m31 - m11*m20*m33 + m10*m21*m33;
- tmp[2][1] = m03*m21*m30 - m01*m23*m30 - m03*m20*m31 + m00*m23*m31 + m01*m20*m33 - m00*m21*m33;
- tmp[2][2] = m01*m13*m30 - m03*m11*m30 + m03*m10*m31 - m00*m13*m31 - m01*m10*m33 + m00*m11*m33;
- tmp[2][3] = m03*m11*m20 - m01*m13*m20 - m03*m10*m21 + m00*m13*m21 + m01*m10*m23 - m00*m11*m23;
- tmp[3][0] = m12*m21*m30 - m11*m22*m30 - m12*m20*m31 + m10*m22*m31 + m11*m20*m32 - m10*m21*m32;
- tmp[3][1] = m01*m22*m30 - m02*m21*m30 + m02*m20*m31 - m00*m22*m31 - m01*m20*m32 + m00*m21*m32;
- tmp[3][2] = m02*m11*m30 - m01*m12*m30 - m02*m10*m31 + m00*m12*m31 + m01*m10*m32 - m00*m11*m32;
- tmp[3][3] = m01*m12*m20 - m02*m11*m20 + m02*m10*m21 - m00*m12*m21 - m01*m10*m22 + m00*m11*m22;
-
- double invDet = 1.0 / det;
for (int i = 0; i < 4; i++) {
+ SkMScalar value = 0;
for (int j = 0; j < 4; j++) {
- inverse->fMat[i][j] = SkDoubleToMScalar(tmp[i][j] * invDet);
+ value += fMat[j][i] * src[j];
}
+ result[i] = SkMScalarToScalar(value);
+ }
+
+ if (storage == result) {
+ memcpy(dst, storage, sizeof(storage));
}
- return true;
}
-///////////////////////////////////////////////////////////////////////////////
+#ifdef SK_MSCALAR_IS_DOUBLE
+
+void SkMatrix44::mapMScalars(const SkMScalar src[4], SkMScalar dst[4]) const {
+ SkMScalar storage[4];
+ SkMScalar* result = (src == dst) ? storage : dst;
-void SkMatrix44::map(const SkScalar src[4], SkScalar dst[4]) const {
- SkScalar result[4];
for (int i = 0; i < 4; i++) {
SkMScalar value = 0;
for (int j = 0; j < 4; j++) {
value += fMat[j][i] * src[j];
}
- result[i] = SkMScalarToScalar(value);
+ result[i] = value;
+ }
+
+ if (storage == result) {
+ memcpy(dst, storage, sizeof(storage));
+ }
+}
+
+#endif
+
+typedef void (*Map2Procf)(const SkMScalar mat[][4], const float src2[], int count, float dst4[]);
+typedef void (*Map2Procd)(const SkMScalar mat[][4], const double src2[], int count, double dst4[]);
+
+static void map2_if(const SkMScalar mat[][4], const float* SK_RESTRICT src2,
+ int count, float* SK_RESTRICT dst4) {
+ for (int i = 0; i < count; ++i) {
+ dst4[0] = src2[0];
+ dst4[1] = src2[1];
+ dst4[2] = 0;
+ dst4[3] = 1;
+ src2 += 2;
+ dst4 += 4;
+ }
+}
+
+static void map2_id(const SkMScalar mat[][4], const double* SK_RESTRICT src2,
+ int count, double* SK_RESTRICT dst4) {
+ for (int i = 0; i < count; ++i) {
+ dst4[0] = src2[0];
+ dst4[1] = src2[1];
+ dst4[2] = 0;
+ dst4[3] = 1;
+ src2 += 2;
+ dst4 += 4;
+ }
+}
+
+static void map2_tf(const SkMScalar mat[][4], const float* SK_RESTRICT src2,
+ int count, float* SK_RESTRICT dst4) {
+ const float mat30 = SkMScalarToFloat(mat[3][0]);
+ const float mat31 = SkMScalarToFloat(mat[3][1]);
+ const float mat32 = SkMScalarToFloat(mat[3][2]);
+ for (int n = 0; n < count; ++n) {
+ dst4[0] = src2[0] + mat30;
+ dst4[1] = src2[1] + mat31;
+ dst4[2] = mat32;
+ dst4[3] = 1;
+ src2 += 2;
+ dst4 += 4;
+ }
+}
+
+static void map2_td(const SkMScalar mat[][4], const double* SK_RESTRICT src2,
+ int count, double* SK_RESTRICT dst4) {
+ for (int n = 0; n < count; ++n) {
+ dst4[0] = src2[0] + mat[3][0];
+ dst4[1] = src2[1] + mat[3][1];
+ dst4[2] = mat[3][2];
+ dst4[3] = 1;
+ src2 += 2;
+ dst4 += 4;
+ }
+}
+
+static void map2_sf(const SkMScalar mat[][4], const float* SK_RESTRICT src2,
+ int count, float* SK_RESTRICT dst4) {
+ const float mat32 = SkMScalarToFloat(mat[3][2]);
+ for (int n = 0; n < count; ++n) {
+ dst4[0] = SkMScalarToFloat(mat[0][0] * src2[0] + mat[3][0]);
+ dst4[1] = SkMScalarToFloat(mat[1][1] * src2[1] + mat[3][1]);
+ dst4[2] = mat32;
+ dst4[3] = 1;
+ src2 += 2;
+ dst4 += 4;
+ }
+}
+
+static void map2_sd(const SkMScalar mat[][4], const double* SK_RESTRICT src2,
+ int count, double* SK_RESTRICT dst4) {
+ for (int n = 0; n < count; ++n) {
+ dst4[0] = mat[0][0] * src2[0] + mat[3][0];
+ dst4[1] = mat[1][1] * src2[1] + mat[3][1];
+ dst4[2] = mat[3][2];
+ dst4[3] = 1;
+ src2 += 2;
+ dst4 += 4;
}
- memcpy(dst, result, sizeof(result));
+}
+
+static void map2_af(const SkMScalar mat[][4], const float* SK_RESTRICT src2,
+ int count, float* SK_RESTRICT dst4) {
+ double r;
+ for (int n = 0; n < count; ++n) {
+ double sx = src2[0];
+ double sy = src2[1];
+ r = mat[0][0] * sx + mat[1][0] * sy + mat[3][0];
+ dst4[0] = SkMScalarToFloat(r);
+ r = mat[0][1] * sx + mat[1][1] * sy + mat[3][1];
+ dst4[1] = SkMScalarToFloat(r);
+ r = mat[0][2] * sx + mat[1][2] * sy + mat[3][2];
+ dst4[2] = SkMScalarToFloat(r);
+ dst4[3] = 1;
+ src2 += 2;
+ dst4 += 4;
+ }
+}
+
+static void map2_ad(const SkMScalar mat[][4], const double* SK_RESTRICT src2,
+ int count, double* SK_RESTRICT dst4) {
+ for (int n = 0; n < count; ++n) {
+ double sx = src2[0];
+ double sy = src2[1];
+ dst4[0] = mat[0][0] * sx + mat[1][0] * sy + mat[3][0];
+ dst4[1] = mat[0][1] * sx + mat[1][1] * sy + mat[3][1];
+ dst4[2] = mat[0][2] * sx + mat[1][2] * sy + mat[3][2];
+ dst4[3] = 1;
+ src2 += 2;
+ dst4 += 4;
+ }
+}
+
+static void map2_pf(const SkMScalar mat[][4], const float* SK_RESTRICT src2,
+ int count, float* SK_RESTRICT dst4) {
+ double r;
+ for (int n = 0; n < count; ++n) {
+ double sx = src2[0];
+ double sy = src2[1];
+ for (int i = 0; i < 4; i++) {
+ r = mat[0][i] * sx + mat[1][i] * sy + mat[3][i];
+ dst4[i] = SkMScalarToFloat(r);
+ }
+ src2 += 2;
+ dst4 += 4;
+ }
+}
+
+static void map2_pd(const SkMScalar mat[][4], const double* SK_RESTRICT src2,
+ int count, double* SK_RESTRICT dst4) {
+ for (int n = 0; n < count; ++n) {
+ double sx = src2[0];
+ double sy = src2[1];
+ for (int i = 0; i < 4; i++) {
+ dst4[i] = mat[0][i] * sx + mat[1][i] * sy + mat[3][i];
+ }
+ src2 += 2;
+ dst4 += 4;
+ }
+}
+
+void SkMatrix44::map2(const float src2[], int count, float dst4[]) const {
+ static const Map2Procf gProc[] = {
+ map2_if, map2_tf, map2_sf, map2_sf, map2_af, map2_af, map2_af, map2_af
+ };
+
+ TypeMask mask = this->getType();
+ Map2Procf proc = (mask & kPerspective_Mask) ? map2_pf : gProc[mask];
+ proc(fMat, src2, count, dst4);
+}
+
+void SkMatrix44::map2(const double src2[], int count, double dst4[]) const {
+ static const Map2Procd gProc[] = {
+ map2_id, map2_td, map2_sd, map2_sd, map2_ad, map2_ad, map2_ad, map2_ad
+ };
+
+ TypeMask mask = this->getType();
+ Map2Procd proc = (mask & kPerspective_Mask) ? map2_pd : gProc[mask];
+ proc(fMat, src2, count, dst4);
}
///////////////////////////////////////////////////////////////////////////////
@@ -357,6 +816,8 @@ void SkMatrix44::dump() const {
///////////////////////////////////////////////////////////////////////////////
+// TODO: make this support src' perspective elements
+//
static void initFromMatrix(SkMScalar dst[4][4], const SkMatrix& src) {
sk_bzero(dst, 16 * sizeof(SkMScalar));
dst[0][0] = SkScalarToMScalar(src[SkMatrix::kMScaleX]);
@@ -374,9 +835,17 @@ SkMatrix44::SkMatrix44(const SkMatrix& src) {
SkMatrix44& SkMatrix44::operator=(const SkMatrix& src) {
initFromMatrix(fMat, src);
+
+ if (src.isIdentity()) {
+ this->setTypeMask(kIdentity_Mask);
+ } else {
+ this->dirtyTypeMask();
+ }
return *this;
}
+// TODO: make this support our perspective elements
+//
SkMatrix44::operator SkMatrix() const {
SkMatrix dst;
dst.reset(); // setup our perspective correctly for identity
diff --git a/src/utils/SkNWayCanvas.cpp b/src/utils/SkNWayCanvas.cpp
index 3b781631a3..d59d559328 100644
--- a/src/utils/SkNWayCanvas.cpp
+++ b/src/utils/SkNWayCanvas.cpp
@@ -144,6 +144,14 @@ bool SkNWayCanvas::clipRect(const SkRect& rect, SkRegion::Op op, bool doAA) {
return this->INHERITED::clipRect(rect, op, doAA);
}
+bool SkNWayCanvas::clipRRect(const SkRRect& rrect, SkRegion::Op op, bool doAA) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->clipRRect(rrect, op, doAA);
+ }
+ return this->INHERITED::clipRRect(rrect, op, doAA);
+}
+
bool SkNWayCanvas::clipPath(const SkPath& path, SkRegion::Op op, bool doAA) {
Iter iter(fList);
while (iter.next()) {
@@ -175,6 +183,13 @@ void SkNWayCanvas::drawPoints(PointMode mode, size_t count, const SkPoint pts[],
}
}
+void SkNWayCanvas::drawOval(const SkRect& rect, const SkPaint& paint) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->drawOval(rect, paint);
+ }
+}
+
void SkNWayCanvas::drawRect(const SkRect& rect, const SkPaint& paint) {
Iter iter(fList);
while (iter.next()) {
@@ -182,6 +197,13 @@ void SkNWayCanvas::drawRect(const SkRect& rect, const SkPaint& paint) {
}
}
+void SkNWayCanvas::drawRRect(const SkRRect& rrect, const SkPaint& paint) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->drawRRect(rrect, paint);
+ }
+}
+
void SkNWayCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
Iter iter(fList);
while (iter.next()) {
diff --git a/src/utils/SkPictureUtils.cpp b/src/utils/SkPictureUtils.cpp
new file mode 100644
index 0000000000..46ca6d6f9e
--- /dev/null
+++ b/src/utils/SkPictureUtils.cpp
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkPictureUtils.h"
+#include "SkCanvas.h"
+#include "SkData.h"
+#include "SkDevice.h"
+#include "SkPixelRef.h"
+#include "SkShader.h"
+
+class PixelRefSet {
+public:
+ PixelRefSet(SkTDArray<SkPixelRef*>* array) : fArray(array) {}
+
+ // This does a linear search on existing pixelrefs, so if this list gets big
+ // we should use a more complex sorted/hashy thing.
+ //
+ void add(SkPixelRef* pr) {
+ uint32_t genID = pr->getGenerationID();
+ if (fGenID.find(genID) < 0) {
+ *fArray->append() = pr;
+ *fGenID.append() = genID;
+// SkDebugf("--- adding [%d] %x %d\n", fArray->count() - 1, pr, genID);
+ } else {
+// SkDebugf("--- already have %x %d\n", pr, genID);
+ }
+ }
+
+private:
+ SkTDArray<SkPixelRef*>* fArray;
+ SkTDArray<uint32_t> fGenID;
+};
+
+static void not_supported() {
+ SkASSERT(!"this method should never be called");
+}
+
+static void nothing_to_do() {}
+
+/**
+ * This device will route all bitmaps (primitives and in shaders) to its PRSet.
+ * It should never actually draw anything, so there need not be any pixels
+ * behind its device-bitmap.
+ */
+class GatherPixelRefDevice : public SkDevice {
+private:
+ PixelRefSet* fPRSet;
+
+ void addBitmap(const SkBitmap& bm) {
+ fPRSet->add(bm.pixelRef());
+ }
+
+ void addBitmapFromPaint(const SkPaint& paint) {
+ SkShader* shader = paint.getShader();
+ if (shader) {
+ SkBitmap bm;
+ if (shader->asABitmap(&bm, NULL, NULL)) {
+ fPRSet->add(bm.pixelRef());
+ }
+ }
+ }
+
+public:
+ GatherPixelRefDevice(const SkBitmap& bm, PixelRefSet* prset) : SkDevice(bm) {
+ fPRSet = prset;
+ }
+
+ virtual void clear(SkColor color) SK_OVERRIDE {
+ nothing_to_do();
+ }
+ virtual void writePixels(const SkBitmap& bitmap, int x, int y,
+ SkCanvas::Config8888 config8888) SK_OVERRIDE {
+ not_supported();
+ }
+
+ virtual void drawPaint(const SkDraw&, const SkPaint& paint) SK_OVERRIDE {
+ this->addBitmapFromPaint(paint);
+ }
+ virtual void drawPoints(const SkDraw&, SkCanvas::PointMode mode, size_t count,
+ const SkPoint[], const SkPaint& paint) SK_OVERRIDE {
+ this->addBitmapFromPaint(paint);
+ }
+ virtual void drawRect(const SkDraw&, const SkRect& r,
+ const SkPaint& paint) SK_OVERRIDE {
+ this->addBitmapFromPaint(paint);
+ }
+ virtual void drawPath(const SkDraw&, const SkPath& path,
+ const SkPaint& paint, const SkMatrix* prePathMatrix,
+ bool pathIsMutable) SK_OVERRIDE {
+ this->addBitmapFromPaint(paint);
+ }
+ virtual void drawBitmap(const SkDraw&, const SkBitmap& bitmap,
+ const SkIRect* srcRectOrNull,
+ const SkMatrix&, const SkPaint&) SK_OVERRIDE {
+ this->addBitmap(bitmap);
+ }
+ virtual void drawBitmapRect(const SkDraw&, const SkBitmap& bitmap,
+ const SkRect* srcOrNull, const SkRect& dst,
+ const SkPaint&) SK_OVERRIDE {
+ this->addBitmap(bitmap);
+ }
+ virtual void drawSprite(const SkDraw&, const SkBitmap& bitmap,
+ int x, int y, const SkPaint& paint) SK_OVERRIDE {
+ this->addBitmap(bitmap);
+ }
+ virtual void drawText(const SkDraw&, const void* text, size_t len,
+ SkScalar x, SkScalar y,
+ const SkPaint& paint) SK_OVERRIDE {
+ this->addBitmapFromPaint(paint);
+ }
+ virtual void drawPosText(const SkDraw&, const void* text, size_t len,
+ const SkScalar pos[], SkScalar constY,
+ int, const SkPaint& paint) SK_OVERRIDE {
+ this->addBitmapFromPaint(paint);
+ }
+ virtual void drawTextOnPath(const SkDraw&, const void* text, size_t len,
+ const SkPath& path, const SkMatrix* matrix,
+ const SkPaint& paint) SK_OVERRIDE {
+ this->addBitmapFromPaint(paint);
+ }
+ virtual void drawVertices(const SkDraw&, SkCanvas::VertexMode, int vertexCount,
+ const SkPoint verts[], const SkPoint texs[],
+ const SkColor colors[], SkXfermode* xmode,
+ const uint16_t indices[], int indexCount,
+ const SkPaint& paint) SK_OVERRIDE {
+ this->addBitmapFromPaint(paint);
+ }
+ virtual void drawDevice(const SkDraw&, SkDevice*, int x, int y,
+ const SkPaint&) SK_OVERRIDE {
+ nothing_to_do();
+ }
+
+protected:
+ virtual bool onReadPixels(const SkBitmap& bitmap,
+ int x, int y,
+ SkCanvas::Config8888 config8888) SK_OVERRIDE {
+ not_supported();
+ return false;
+ }
+};
+
+class NoSaveLayerCanvas : public SkCanvas {
+public:
+ NoSaveLayerCanvas(SkDevice* device) : INHERITED(device) {}
+
+ // turn saveLayer() into save() for speed, should not affect correctness.
+ virtual int saveLayer(const SkRect* bounds, const SkPaint* paint,
+ SaveFlags flags) SK_OVERRIDE {
+
+ // Like SkPictureRecord, we don't want to create layers, but we do need
+ // to respect the save and (possibly) its rect-clip.
+
+ int count = this->INHERITED::save(flags);
+ if (bounds) {
+ this->INHERITED::clipRectBounds(bounds, flags, NULL);
+ }
+ return count;
+ }
+
+ // disable aa for speed
+ virtual bool clipRect(const SkRect& rect, SkRegion::Op op,
+ bool doAA) SK_OVERRIDE {
+ return this->INHERITED::clipRect(rect, op, false);
+ }
+
+ // for speed, just respect the bounds, and disable AA. May give us a few
+ // false positives and negatives.
+ virtual bool clipPath(const SkPath& path, SkRegion::Op op,
+ bool doAA) SK_OVERRIDE {
+ return this->INHERITED::clipRect(path.getBounds(), op, false);
+ }
+
+private:
+ typedef SkCanvas INHERITED;
+};
+
+SkData* SkPictureUtils::GatherPixelRefs(SkPicture* pict, const SkRect& area) {
+ if (NULL == pict) {
+ return NULL;
+ }
+
+ // this test also handles if either area or pict's width/height are empty
+ if (!SkRect::Intersects(area,
+ SkRect::MakeWH(SkIntToScalar(pict->width()),
+ SkIntToScalar(pict->height())))) {
+ return NULL;
+ }
+
+ SkTDArray<SkPixelRef*> array;
+ PixelRefSet prset(&array);
+
+ SkBitmap emptyBitmap;
+ emptyBitmap.setConfig(SkBitmap::kARGB_8888_Config, pict->width(), pict->height());
+ // note: we do not set any pixels (shouldn't need to)
+
+ GatherPixelRefDevice device(emptyBitmap, &prset);
+ NoSaveLayerCanvas canvas(&device);
+
+ canvas.clipRect(area, SkRegion::kIntersect_Op, false);
+ canvas.drawPicture(*pict);
+
+ SkData* data = NULL;
+ int count = array.count();
+ if (count > 0) {
+ data = SkData::NewFromMalloc(array.detach(), count * sizeof(SkPixelRef*));
+ }
+ return data;
+}
+
diff --git a/src/utils/SkProxyCanvas.cpp b/src/utils/SkProxyCanvas.cpp
index e245c73272..4af509df4f 100644
--- a/src/utils/SkProxyCanvas.cpp
+++ b/src/utils/SkProxyCanvas.cpp
@@ -62,6 +62,10 @@ bool SkProxyCanvas::clipRect(const SkRect& rect, SkRegion::Op op, bool doAA) {
return fProxy->clipRect(rect, op, doAA);
}
+bool SkProxyCanvas::clipRRect(const SkRRect& rrect, SkRegion::Op op, bool doAA) {
+ return fProxy->clipRRect(rrect, op, doAA);
+}
+
bool SkProxyCanvas::clipPath(const SkPath& path, SkRegion::Op op, bool doAA) {
return fProxy->clipPath(path, op, doAA);
}
@@ -79,10 +83,18 @@ void SkProxyCanvas::drawPoints(PointMode mode, size_t count,
fProxy->drawPoints(mode, count, pts, paint);
}
+void SkProxyCanvas::drawOval(const SkRect& rect, const SkPaint& paint) {
+ fProxy->drawOval(rect, paint);
+}
+
void SkProxyCanvas::drawRect(const SkRect& rect, const SkPaint& paint) {
fProxy->drawRect(rect, paint);
}
+void SkProxyCanvas::drawRRect(const SkRRect& rrect, const SkPaint& paint) {
+ fProxy->drawRRect(rrect, paint);
+}
+
void SkProxyCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
fProxy->drawPath(path, paint);
}
diff --git a/src/utils/SkThreadPool.cpp b/src/utils/SkThreadPool.cpp
new file mode 100644
index 0000000000..78cb417d07
--- /dev/null
+++ b/src/utils/SkThreadPool.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkThreadPool.h"
+#include "SkRunnable.h"
+#include "SkThreadUtils.h"
+
+SkThreadPool::SkThreadPool(const int count)
+: fDone(false) {
+ // Create count threads, all running SkThreadPool::Loop.
+ for (int i = 0; i < count; i++) {
+ SkThread* thread = SkNEW_ARGS(SkThread, (&SkThreadPool::Loop, this));
+ *fThreads.append() = thread;
+ thread->start();
+ }
+}
+
+SkThreadPool::~SkThreadPool() {
+ fDone = true;
+ fReady.lock();
+ fReady.broadcast();
+ fReady.unlock();
+
+ // Wait for all threads to stop.
+ for (int i = 0; i < fThreads.count(); i++) {
+ fThreads[i]->join();
+ SkDELETE(fThreads[i]);
+ }
+}
+
+/*static*/ void SkThreadPool::Loop(void* arg) {
+ // The SkThreadPool passes itself as arg to each thread as they're created.
+ SkThreadPool* pool = static_cast<SkThreadPool*>(arg);
+
+ while (true) {
+ // We have to be holding the lock to read the queue and to call wait.
+ pool->fReady.lock();
+ while(pool->fQueue.isEmpty()) {
+ // Is it time to die?
+ if (pool->fDone) {
+ pool->fReady.unlock();
+ return;
+ }
+ // wait yields the lock while waiting, but will have it again when awoken.
+ pool->fReady.wait();
+ }
+ // We've got the lock back here, no matter if we ran wait or not.
+
+ // The queue is not empty, so we have something to run. Claim it.
+ LinkedRunnable* r = pool->fQueue.tail();
+
+ pool->fQueue.remove(r);
+
+ // Having claimed our SkRunnable, we now give up the lock while we run it.
+ // Otherwise, we'd only ever do work on one thread at a time, which rather
+ // defeats the point of this code.
+ pool->fReady.unlock();
+
+ // OK, now really do the work.
+ r->fRunnable->run();
+ SkDELETE(r);
+ }
+
+ SkASSERT(false); // Unreachable. The only exit happens when pool->fDone.
+}
+
+void SkThreadPool::add(SkRunnable* r) {
+ if (NULL == r) {
+ return;
+ }
+
+ // If we don't have any threads, obligingly just run the thing now.
+ if (fThreads.isEmpty()) {
+ return r->run();
+ }
+
+ // We have some threads. Queue it up!
+ fReady.lock();
+ LinkedRunnable* linkedRunnable = SkNEW(LinkedRunnable);
+ linkedRunnable->fRunnable = r;
+ fQueue.addToHead(linkedRunnable);
+ fReady.signal();
+ fReady.unlock();
+}
diff --git a/src/utils/android/ashmem.c b/src/utils/android/ashmem.c
new file mode 100644
index 0000000000..0e1e81669d
--- /dev/null
+++ b/src/utils/android/ashmem.c
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2008 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/*
+ * Implementation of the user-space ashmem API for devices, which have our
+ * ashmem-enabled kernel. See ashmem-sim.c for the "fake" tmp-based version,
+ * used by the simulator.
+ */
+
+#include <android/ashmem.h>
+
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+
+#include <linux/ashmem.h>
+
+#define ASHMEM_DEVICE "/dev/ashmem"
+
+/*
+ * ashmem_create_region - creates a new ashmem region and returns the file
+ * descriptor, or <0 on error
+ *
+ * `name' is an optional label to give the region (visible in /proc/pid/maps)
+ * `size' is the size of the region, in page-aligned bytes
+ */
+int ashmem_create_region(const char *name, size_t size)
+{
+ int fd, ret;
+
+ fd = open(ASHMEM_DEVICE, O_RDWR);
+ if (fd < 0)
+ return fd;
+
+ if (name) {
+ char buf[ASHMEM_NAME_LEN];
+
+ strlcpy(buf, name, sizeof(buf));
+ ret = ioctl(fd, ASHMEM_SET_NAME, buf);
+ if (ret < 0)
+ goto error;
+ }
+
+ ret = ioctl(fd, ASHMEM_SET_SIZE, size);
+ if (ret < 0)
+ goto error;
+
+ return fd;
+
+error:
+ close(fd);
+ return ret;
+}
+
+int ashmem_set_prot_region(int fd, int prot)
+{
+ return ioctl(fd, ASHMEM_SET_PROT_MASK, prot);
+}
+
+int ashmem_pin_region(int fd, size_t offset, size_t len)
+{
+ struct ashmem_pin pin = { offset, len };
+ return ioctl(fd, ASHMEM_PIN, &pin);
+}
+
+int ashmem_unpin_region(int fd, size_t offset, size_t len)
+{
+ struct ashmem_pin pin = { offset, len };
+ return ioctl(fd, ASHMEM_UNPIN, &pin);
+}
+
+int ashmem_get_size_region(int fd)
+{
+ return ioctl(fd, ASHMEM_GET_SIZE, NULL);
+}
diff --git a/src/utils/android/ashmem.h b/src/utils/android/ashmem.h
new file mode 100644
index 0000000000..2d6c0b5a47
--- /dev/null
+++ b/src/utils/android/ashmem.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2008 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef _CUTILS_ASHMEM_H
+#define _CUTILS_ASHMEM_H
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int ashmem_create_region(const char *name, size_t size);
+int ashmem_set_prot_region(int fd, int prot);
+int ashmem_pin_region(int fd, size_t offset, size_t len);
+int ashmem_unpin_region(int fd, size_t offset, size_t len);
+int ashmem_get_size_region(int fd);
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifndef __ASHMEMIOC /* in case someone included <linux/ashmem.h> too */
+
+#define ASHMEM_NAME_LEN 256
+
+#define ASHMEM_NAME_DEF "dev/ashmem"
+
+/* Return values from ASHMEM_PIN: Was the mapping purged while unpinned? */
+#define ASHMEM_NOT_PURGED 0
+#define ASHMEM_WAS_PURGED 1
+
+/* Return values from ASHMEM_UNPIN: Is the mapping now pinned or unpinned? */
+#define ASHMEM_IS_UNPINNED 0
+#define ASHMEM_IS_PINNED 1
+
+#endif /* ! __ASHMEMIOC */
+
+#endif /* _CUTILS_ASHMEM_H */
diff --git a/src/utils/cityhash/README b/src/utils/cityhash/README
new file mode 100644
index 0000000000..d93928857f
--- /dev/null
+++ b/src/utils/cityhash/README
@@ -0,0 +1,2 @@
+This directory contains files needed to build third_party/externals/cityhash
+(such as the config.h file that would normally be created by autoconf)
diff --git a/src/utils/cityhash/config.h b/src/utils/cityhash/config.h
new file mode 100644
index 0000000000..bd3641658e
--- /dev/null
+++ b/src/utils/cityhash/config.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**
+ * Converts from Skia build flags to the macro definitions cityhash normally
+ * gets from autoconf.
+ */
+
+#include "SkTypes.h"
+
+#ifdef SK_CPU_BENDIAN
+ #define WORDS_BIGENDIAN 1
+#endif
diff --git a/src/utils/mac/SkCreateCGImageRef.cpp b/src/utils/mac/SkCreateCGImageRef.cpp
new file mode 100644
index 0000000000..abad8cb440
--- /dev/null
+++ b/src/utils/mac/SkCreateCGImageRef.cpp
@@ -0,0 +1,241 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SkCGUtils.h"
+#include "SkBitmap.h"
+#include "SkColorPriv.h"
+
+static void SkBitmap_ReleaseInfo(void* info, const void* pixelData, size_t size) {
+ SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(info);
+ delete bitmap;
+}
+
+#define HAS_ARGB_SHIFTS(a, r, g, b) \
+ (SK_A32_SHIFT == (a) && SK_R32_SHIFT == (r) \
+ && SK_G32_SHIFT == (g) && SK_B32_SHIFT == (b))
+
+static bool getBitmapInfo(const SkBitmap& bm,
+ size_t* bitsPerComponent,
+ CGBitmapInfo* info,
+ bool* upscaleTo32) {
+ if (upscaleTo32) {
+ *upscaleTo32 = false;
+ }
+
+ switch (bm.config()) {
+ case SkBitmap::kRGB_565_Config:
+ if (upscaleTo32) {
+ *upscaleTo32 = true;
+ }
+ // fall through
+ case SkBitmap::kARGB_8888_Config:
+ *bitsPerComponent = 8;
+#if defined(SK_CPU_LENDIAN) && HAS_ARGB_SHIFTS(24, 0, 8, 16) \
+|| defined(SK_CPU_BENDIAN) && HAS_ARGB_SHIFTS(0, 24, 16, 8)
+ *info = kCGBitmapByteOrder32Big;
+ if (bm.isOpaque()) {
+ *info |= kCGImageAlphaNoneSkipLast;
+ } else {
+ *info |= kCGImageAlphaPremultipliedLast;
+ }
+#elif defined(SK_CPU_LENDIAN) && HAS_ARGB_SHIFTS(24, 16, 8, 0) \
+|| defined(SK_CPU_BENDIAN) && HAS_ARGB_SHIFTS(24, 16, 8, 0)
+ // Matches the CGBitmapInfo that Apple recommends for best
+ // performance, used by google chrome.
+ *info = kCGBitmapByteOrder32Little;
+ if (bm.isOpaque()) {
+ *info |= kCGImageAlphaNoneSkipFirst;
+ } else {
+ *info |= kCGImageAlphaPremultipliedFirst;
+ }
+#else
+ // ...add more formats as required...
+#warning Cannot convert SkBitmap to CGImageRef with these shiftmasks. \
+This will probably not work.
+ // Legacy behavior. Perhaps turn this into an error at some
+ // point.
+ *info = kCGBitmapByteOrder32Big;
+ if (bm.isOpaque()) {
+ *info |= kCGImageAlphaNoneSkipLast;
+ } else {
+ *info |= kCGImageAlphaPremultipliedLast;
+ }
+#endif
+ break;
+#if 0
+ case SkBitmap::kRGB_565_Config:
+ // doesn't see quite right. Are they thinking 1555?
+ *bitsPerComponent = 5;
+ *info = kCGBitmapByteOrder16Little | kCGImageAlphaNone;
+ break;
+#endif
+ case SkBitmap::kARGB_4444_Config:
+ *bitsPerComponent = 4;
+ *info = kCGBitmapByteOrder16Little;
+ if (bm.isOpaque()) {
+ *info |= kCGImageAlphaNoneSkipLast;
+ } else {
+ *info |= kCGImageAlphaPremultipliedLast;
+ }
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+static SkBitmap* prepareForImageRef(const SkBitmap& bm,
+ size_t* bitsPerComponent,
+ CGBitmapInfo* info) {
+ bool upscaleTo32;
+ if (!getBitmapInfo(bm, bitsPerComponent, info, &upscaleTo32)) {
+ return NULL;
+ }
+
+ SkBitmap* copy;
+ if (upscaleTo32) {
+ copy = new SkBitmap;
+ // here we make a ceep copy of the pixels, since CG won't take our
+ // 565 directly
+ bm.copyTo(copy, SkBitmap::kARGB_8888_Config);
+ } else {
+ copy = new SkBitmap(bm);
+ }
+ return copy;
+}
+
+#undef HAS_ARGB_SHIFTS
+
+CGImageRef SkCreateCGImageRefWithColorspace(const SkBitmap& bm,
+ CGColorSpaceRef colorSpace) {
+ size_t bitsPerComponent SK_INIT_TO_AVOID_WARNING;
+ CGBitmapInfo info SK_INIT_TO_AVOID_WARNING;
+
+ SkBitmap* bitmap = prepareForImageRef(bm, &bitsPerComponent, &info);
+ if (NULL == bitmap) {
+ return NULL;
+ }
+
+ const int w = bitmap->width();
+ const int h = bitmap->height();
+ const size_t s = bitmap->getSize();
+
+ // our provider "owns" the bitmap*, and will take care of deleting it
+ // we initially lock it, so we can access the pixels. The bitmap will be deleted in the release
+ // proc, which will in turn unlock the pixels
+ bitmap->lockPixels();
+ CGDataProviderRef dataRef = CGDataProviderCreateWithData(bitmap, bitmap->getPixels(), s,
+ SkBitmap_ReleaseInfo);
+
+ bool releaseColorSpace = false;
+ if (NULL == colorSpace) {
+ colorSpace = CGColorSpaceCreateDeviceRGB();
+ releaseColorSpace = true;
+ }
+
+ CGImageRef ref = CGImageCreate(w, h, bitsPerComponent,
+ bitmap->bytesPerPixel() * 8,
+ bitmap->rowBytes(), colorSpace, info, dataRef,
+ NULL, false, kCGRenderingIntentDefault);
+
+ if (releaseColorSpace) {
+ CGColorSpaceRelease(colorSpace);
+ }
+ CGDataProviderRelease(dataRef);
+ return ref;
+}
+
+void SkCGDrawBitmap(CGContextRef cg, const SkBitmap& bm, float x, float y) {
+ CGImageRef img = SkCreateCGImageRef(bm);
+
+ if (img) {
+ CGRect r = CGRectMake(0, 0, bm.width(), bm.height());
+
+ CGContextSaveGState(cg);
+ CGContextTranslateCTM(cg, x, r.size.height + y);
+ CGContextScaleCTM(cg, 1, -1);
+
+ CGContextDrawImage(cg, r, img);
+
+ CGContextRestoreGState(cg);
+
+ CGImageRelease(img);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkStream.h"
+
+class SkAutoPDFRelease {
+public:
+ SkAutoPDFRelease(CGPDFDocumentRef doc) : fDoc(doc) {}
+ ~SkAutoPDFRelease() {
+ if (fDoc) {
+ CGPDFDocumentRelease(fDoc);
+ }
+ }
+private:
+ CGPDFDocumentRef fDoc;
+};
+
+static void CGDataProviderReleaseData_FromMalloc(void*, const void* data,
+ size_t size) {
+ sk_free((void*)data);
+}
+
+bool SkPDFDocumentToBitmap(SkStream* stream, SkBitmap* output) {
+ size_t size = stream->getLength();
+ void* ptr = sk_malloc_throw(size);
+ stream->read(ptr, size);
+ CGDataProviderRef data = CGDataProviderCreateWithData(NULL, ptr, size,
+ CGDataProviderReleaseData_FromMalloc);
+ if (NULL == data) {
+ return false;
+ }
+
+ CGPDFDocumentRef pdf = CGPDFDocumentCreateWithProvider(data);
+ CGDataProviderRelease(data);
+ if (NULL == pdf) {
+ return false;
+ }
+ SkAutoPDFRelease releaseMe(pdf);
+
+ CGPDFPageRef page = CGPDFDocumentGetPage(pdf, 1);
+ if (NULL == page) {
+ return false;
+ }
+
+ CGRect bounds = CGPDFPageGetBoxRect(page, kCGPDFMediaBox);
+
+ int w = (int)CGRectGetWidth(bounds);
+ int h = (int)CGRectGetHeight(bounds);
+
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config, w, h);
+ bitmap.allocPixels();
+ bitmap.eraseColor(SK_ColorWHITE);
+
+ size_t bitsPerComponent;
+ CGBitmapInfo info;
+ getBitmapInfo(bitmap, &bitsPerComponent, &info, NULL);
+
+ CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
+ CGContextRef ctx = CGBitmapContextCreate(bitmap.getPixels(), w, h,
+ bitsPerComponent, bitmap.rowBytes(),
+ cs, info);
+ CGColorSpaceRelease(cs);
+
+ if (ctx) {
+ CGContextDrawPDFPage(ctx, page);
+ CGContextRelease(ctx);
+ }
+
+ output->swap(bitmap);
+ return true;
+}
+
diff --git a/src/views/win/skia_win.cpp b/src/views/win/skia_win.cpp
new file mode 100644
index 0000000000..327b0cdb96
--- /dev/null
+++ b/src/views/win/skia_win.cpp
@@ -0,0 +1,205 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include <Windows.h>
+#include <tchar.h>
+
+#include "SkApplication.h"
+
+#define MAX_LOADSTRING 100
+
+// Global Variables:
+HINSTANCE hInst; // current instance
+TCHAR szTitle[] = _T("SampleApp"); // The title bar text
+TCHAR szWindowClass[] = _T("SAMPLEAPP"); // the main window class name
+
+// Forward declarations of functions included in this code module:
+ATOM MyRegisterClass(HINSTANCE hInstance);
+BOOL InitInstance(HINSTANCE, int, LPTSTR);
+LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
+INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
+
+int APIENTRY _tWinMain(HINSTANCE hInstance,
+ HINSTANCE hPrevInstance,
+ LPTSTR lpCmdLine,
+ int nCmdShow)
+{
+ UNREFERENCED_PARAMETER(hPrevInstance);
+
+ MSG msg;
+
+ // Initialize global strings
+ MyRegisterClass(hInstance);
+
+ // Perform application initialization:
+ if (!InitInstance (hInstance, nCmdShow, lpCmdLine))
+ {
+ return FALSE;
+ }
+
+ // Main message loop:
+ while (GetMessage(&msg, NULL, 0, 0))
+ {
+ if (true)
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ }
+
+ application_term();
+
+ return (int) msg.wParam;
+}
+
+
+
+//
+// FUNCTION: MyRegisterClass()
+//
+// PURPOSE: Registers the window class.
+//
+// COMMENTS:
+//
+// This function and its usage are only necessary if you want this code
+// to be compatible with Win32 systems prior to the 'RegisterClassEx'
+// function that was added to Windows 95. It is important to call this function
+// so that the application will get 'well formed' small icons associated
+// with it.
+//
+ATOM MyRegisterClass(HINSTANCE hInstance)
+{
+ WNDCLASSEX wcex;
+
+ wcex.cbSize = sizeof(WNDCLASSEX);
+
+ wcex.style = CS_HREDRAW | CS_VREDRAW;
+ wcex.lpfnWndProc = WndProc;
+ wcex.cbClsExtra = 0;
+ wcex.cbWndExtra = 0;
+ wcex.hInstance = hInstance;
+ wcex.hIcon = NULL;
+ wcex.hCursor = NULL;
+ wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
+ wcex.lpszMenuName = NULL;
+ wcex.lpszClassName = szWindowClass;
+ wcex.hIconSm = NULL;
+
+ return RegisterClassEx(&wcex);
+}
+
+#include "SkOSWindow_Win.h"
+extern SkOSWindow* create_sk_window(void* hwnd, int argc, char** argv);
+
+static SkOSWindow* gSkWind;
+
+char* tchar_to_utf8(const TCHAR* str) {
+#ifdef _UNICODE
+ int size = WideCharToMultiByte(CP_UTF8, 0, str, wcslen(str), NULL, 0, NULL, NULL);
+ char* str8 = (char*) malloc(size+1);
+ WideCharToMultiByte(CP_UTF8, 0, str, wcslen(str), str8, size, NULL, NULL);
+ str8[size] = '\0';
+ return str8;
+#else
+ return _strdup(str);
+#endif
+}
+
+//
+// FUNCTION: InitInstance(HINSTANCE, int, LPTSTR)
+//
+// PURPOSE: Saves instance handle and creates main window
+//
+// COMMENTS:
+//
+// In this function, we save the instance handle in a global variable and
+// create and display the main program window.
+//
+
+
+BOOL InitInstance(HINSTANCE hInstance, int nCmdShow, LPTSTR lpCmdLine)
+{
+ application_init();
+
+ hInst = hInstance; // Store instance handle in our global variable
+
+ HWND hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
+ CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
+
+ if (!hWnd)
+ {
+ return FALSE;
+ }
+
+ char* argv[4096];
+ int argc = 0;
+ TCHAR exename[1024], *next;
+ int exenameLen = GetModuleFileName(NULL, exename, 1024);
+ argv[argc++] = tchar_to_utf8(exename);
+ TCHAR* arg = _tcstok_s(lpCmdLine, _T(" "), &next);
+ while (arg != NULL) {
+ argv[argc++] = tchar_to_utf8(arg);
+ arg = _tcstok_s(NULL, _T(" "), &next);
+ }
+
+ gSkWind = create_sk_window(hWnd, argc, argv);
+ for (int i = 0; i < argc; ++i) {
+ free(argv[i]);
+ }
+ ShowWindow(hWnd, nCmdShow);
+ UpdateWindow(hWnd);
+
+ return TRUE;
+}
+
+//
+// FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
+//
+// PURPOSE: Processes messages for the main window.
+//
+// WM_COMMAND - process the application menu
+// WM_PAINT - Paint the main window
+// WM_DESTROY - post a quit message and return
+//
+//
+LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ switch (message) {
+ case WM_COMMAND:
+ return DefWindowProc(hWnd, message, wParam, lParam);
+ case WM_DESTROY:
+ PostQuitMessage(0);
+ break;
+ default:
+ if (gSkWind->wndProc(hWnd, message, wParam, lParam)) {
+ return 0;
+ } else {
+ return DefWindowProc(hWnd, message, wParam, lParam);
+ }
+ }
+ return 0;
+}
+
+// Message handler for about box.
+INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ UNREFERENCED_PARAMETER(lParam);
+ switch (message)
+ {
+ case WM_INITDIALOG:
+ return (INT_PTR)TRUE;
+
+ case WM_COMMAND:
+ if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
+ {
+ EndDialog(hDlg, LOWORD(wParam));
+ return (INT_PTR)TRUE;
+ }
+ break;
+ }
+ return (INT_PTR)FALSE;
+}
diff --git a/tests/Android.mk b/tests/Android.mk
index 27f08dd3cd..fc814b998b 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -6,11 +6,13 @@ LOCAL_SRC_FILES:= \
AAClipTest.cpp \
AtomicTest.cpp \
BitmapCopyTest.cpp \
+ BitmapFactoryTest.cpp \
BitmapGetColorTest.cpp \
+ BitmapHeapTest.cpp \
+ BitmapTransformerTest.cpp \
BitSetTest.cpp \
BlitRowTest.cpp \
BlurTest.cpp \
- CanvasTest.cpp \
ClampRangeTest.cpp \
ClipCacheTest.cpp \
ClipCubicTest.cpp \
@@ -39,6 +41,7 @@ LOCAL_SRC_FILES:= \
GrMemoryPoolTest.cpp \
HashCacheTest.cpp \
InfRectTest.cpp \
+ LListTest.cpp \
MathTest.cpp \
Matrix44Test.cpp \
MatrixTest.cpp \
@@ -61,6 +64,7 @@ LOCAL_SRC_FILES:= \
RefCntTest.cpp \
RefDictTest.cpp \
RegionTest.cpp \
+ RoundRectTest.cpp \
RTreeTest.cpp \
ScalarTest.cpp \
ShaderOpacityTest.cpp \
@@ -70,10 +74,10 @@ LOCAL_SRC_FILES:= \
SrcOverTest.cpp \
StreamTest.cpp \
StringTest.cpp \
- TDLinkedListTest.cpp \
+ StrokeTest.cpp \
Test.cpp \
- Test.h \
TestSize.cpp \
+ TileGridTest.cpp \
TLSTest.cpp \
UnicodeTest.cpp \
UtilsTest.cpp \
@@ -89,6 +93,8 @@ LOCAL_SRC_FILES += \
# TODO: tests that currently are causing build problems
#LOCAL_SRC_FILES += \
# AnnotationTest.cpp \
+# CanvasTest.cpp \
+# ChecksumTest.cpp \
# PDFPrimitivesTest.cpp \
# PictureUtilsTest.cpp \
# ToUnicode.cpp
diff --git a/tests/AnnotationTest.cpp b/tests/AnnotationTest.cpp
index 17da6226fe..586525a9e8 100644
--- a/tests/AnnotationTest.cpp
+++ b/tests/AnnotationTest.cpp
@@ -16,7 +16,7 @@ static void test_nodraw(skiatest::Reporter* reporter) {
SkBitmap bm;
bm.setConfig(SkBitmap::kARGB_8888_Config, 10, 10);
bm.allocPixels();
- bm.eraseColor(0);
+ bm.eraseColor(SK_ColorTRANSPARENT);
SkCanvas canvas(bm);
SkRect r = SkRect::MakeWH(SkIntToScalar(10), SkIntToScalar(10));
diff --git a/tests/BitmapFactoryTest.cpp b/tests/BitmapFactoryTest.cpp
new file mode 100644
index 0000000000..b24fd258ca
--- /dev/null
+++ b/tests/BitmapFactoryTest.cpp
@@ -0,0 +1,76 @@
+
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBitmap.h"
+#include "SkBitmapFactory.h"
+#include "SkCanvas.h"
+#include "SkColor.h"
+#include "SkData.h"
+#include "SkImageEncoder.h"
+#include "SkPaint.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+#include "Test.h"
+
+static SkBitmap* create_bitmap() {
+ SkBitmap* bm = SkNEW(SkBitmap);
+ const int W = 100, H = 100;
+ bm->setConfig(SkBitmap::kARGB_8888_Config, W, H);
+ bm->allocPixels();
+ bm->eraseColor(SK_ColorBLACK);
+ SkCanvas canvas(*bm);
+ SkPaint paint;
+ paint.setColor(SK_ColorBLUE);
+ canvas.drawRectCoords(0, 0, SkIntToScalar(W/2), SkIntToScalar(H/2), paint);
+ return bm;
+}
+
+static SkData* create_data_from_bitmap(const SkBitmap& bm) {
+ SkDynamicMemoryWStream stream;
+ if (SkImageEncoder::EncodeStream(&stream, bm, SkImageEncoder::kPNG_Type, 100)) {
+ return stream.copyToData();
+ }
+ return NULL;
+}
+
+static void assert_bounds_equal(skiatest::Reporter* reporter, const SkBitmap& bm1,
+ const SkBitmap& bm2) {
+ REPORTER_ASSERT(reporter, bm1.width() == bm2.width());
+ REPORTER_ASSERT(reporter, bm1.height() == bm2.height());
+}
+
+static void TestBitmapFactory(skiatest::Reporter* reporter) {
+ SkAutoTDelete<SkBitmap> bitmap(create_bitmap());
+ SkASSERT(bitmap.get() != NULL);
+
+ SkAutoDataUnref encodedBitmap(create_data_from_bitmap(*bitmap.get()));
+ if (encodedBitmap.get() == NULL) {
+ // Encoding failed.
+ return;
+ }
+
+ SkBitmap bitmapFromFactory;
+ bool success = SkBitmapFactory::DecodeBitmap(&bitmapFromFactory, encodedBitmap);
+ // This assumes that if the encoder worked, the decoder should also work, so the above call
+ // should not fail.
+ REPORTER_ASSERT(reporter, success);
+ assert_bounds_equal(reporter, *bitmap.get(), bitmapFromFactory);
+ REPORTER_ASSERT(reporter, bitmapFromFactory.pixelRef() != NULL);
+
+ // When only requesting that the bounds be decoded, the bounds should be set properly while
+ // the pixels should be empty.
+ SkBitmap boundedBitmap;
+ success = SkBitmapFactory::DecodeBitmap(&boundedBitmap, encodedBitmap,
+ SkBitmapFactory::kDecodeBoundsOnly_Constraint);
+ REPORTER_ASSERT(reporter, success);
+ assert_bounds_equal(reporter, *bitmap.get(), boundedBitmap);
+ REPORTER_ASSERT(reporter, boundedBitmap.pixelRef() == NULL);
+}
+
+#include "TestClassDef.h"
+DEFINE_TESTCLASS("BitmapFactory", TestBitmapFactoryClass, TestBitmapFactory)
diff --git a/tests/BitmapHeapTest.cpp b/tests/BitmapHeapTest.cpp
new file mode 100644
index 0000000000..34a2984d40
--- /dev/null
+++ b/tests/BitmapHeapTest.cpp
@@ -0,0 +1,96 @@
+
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBitmap.h"
+#include "SkBitmapHeap.h"
+#include "SkColor.h"
+#include "SkFlattenable.h"
+#include "SkOrderedWriteBuffer.h"
+#include "SkPictureFlat.h"
+#include "SkRefCnt.h"
+#include "SkShader.h"
+#include "Test.h"
+
+class FlatDictionary : public SkFlatDictionary<SkShader> {
+
+public:
+ FlatDictionary(SkFlatController* controller)
+ : SkFlatDictionary<SkShader>(controller) {
+ fFlattenProc = &flattenFlattenableProc;
+ // No need for an unflattenProc
+ }
+ static void flattenFlattenableProc(SkOrderedWriteBuffer& buffer, const void* obj) {
+ buffer.writeFlattenable((SkFlattenable*)obj);
+ }
+};
+
+class SkBitmapHeapTester {
+
+public:
+ static int32_t GetRefCount(const SkBitmapHeapEntry* entry) {
+ return entry->fRefCount;
+ }
+};
+
+static void TestBitmapHeap(skiatest::Reporter* reporter) {
+ // Create a bitmap shader.
+ SkBitmap bm;
+ bm.setConfig(SkBitmap::kARGB_8888_Config, 2, 2);
+ bm.allocPixels();
+ bm.eraseColor(SK_ColorRED);
+ uint32_t* pixel = bm.getAddr32(1,0);
+ *pixel = SK_ColorBLUE;
+
+ SkShader* bitmapShader = SkShader::CreateBitmapShader(bm, SkShader::kRepeat_TileMode,
+ SkShader::kRepeat_TileMode);
+ SkAutoTUnref<SkShader> aur(bitmapShader);
+
+ // Flatten, storing it in the bitmap heap.
+ SkBitmapHeap heap(1, 1);
+ SkChunkFlatController controller(1024);
+ controller.setBitmapStorage(&heap);
+ FlatDictionary dictionary(&controller);
+
+ // Dictionary and heap start off empty.
+ REPORTER_ASSERT(reporter, heap.count() == 0);
+ REPORTER_ASSERT(reporter, dictionary.count() == 0);
+
+ heap.deferAddingOwners();
+ int index = dictionary.find(*bitmapShader);
+ heap.endAddingOwnersDeferral(true);
+
+ // The dictionary and heap should now each have one entry.
+ REPORTER_ASSERT(reporter, 1 == index);
+ REPORTER_ASSERT(reporter, heap.count() == 1);
+ REPORTER_ASSERT(reporter, dictionary.count() == 1);
+
+ // The bitmap entry's refcount should be 1, then 0 after release.
+ SkBitmapHeapEntry* entry = heap.getEntry(0);
+ REPORTER_ASSERT(reporter, SkBitmapHeapTester::GetRefCount(entry) == 1);
+
+ entry->releaseRef();
+ REPORTER_ASSERT(reporter, SkBitmapHeapTester::GetRefCount(entry) == 0);
+
+ // Now clear out the heap, after which it should be empty.
+ heap.freeMemoryIfPossible(~0U);
+ REPORTER_ASSERT(reporter, heap.count() == 0);
+
+ // Now attempt to flatten the shader again.
+ heap.deferAddingOwners();
+ index = dictionary.find(*bitmapShader);
+ heap.endAddingOwnersDeferral(false);
+
+ // The dictionary should report the same index since the new entry is identical.
+ // The bitmap heap should contain the bitmap, but with no references.
+ REPORTER_ASSERT(reporter, 1 == index);
+ REPORTER_ASSERT(reporter, heap.count() == 1);
+ REPORTER_ASSERT(reporter, SkBitmapHeapTester::GetRefCount(heap.getEntry(0)) == 0);
+}
+
+#include "TestClassDef.h"
+DEFINE_TESTCLASS("BitmapHeap", TestBitmapHeapClass, TestBitmapHeap)
diff --git a/tests/BitmapTransformerTest.cpp b/tests/BitmapTransformerTest.cpp
new file mode 100644
index 0000000000..d9ce696b66
--- /dev/null
+++ b/tests/BitmapTransformerTest.cpp
@@ -0,0 +1,97 @@
+
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**
+ * Tests for SkBitmapTransformer.h and SkBitmapTransformer.cpp
+ */
+
+#include "Test.h"
+#include "SkBitmap.h"
+#include "SkBitmapTransformer.h"
+
+namespace skiatest {
+ class BitmapTransformerTestClass : public Test {
+ public:
+ static Test* Factory(void*) {return SkNEW(BitmapTransformerTestClass); }
+ protected:
+ virtual void onGetName(SkString* name) { name->set("BitmapTransformer"); }
+ virtual void onRun(Reporter* reporter) {
+ this->fReporter = reporter;
+ RunTest();
+ }
+ private:
+ void RunTest() {
+ SkBitmap bitmap;
+ SkBitmap::Config supportedConfig = SkBitmap::kARGB_8888_Config;
+ SkBitmap::Config unsupportedConfig = SkBitmap::kARGB_4444_Config;
+ SkBitmapTransformer::PixelFormat supportedPixelFormat =
+ SkBitmapTransformer::kARGB_8888_Premul_PixelFormat;
+ const int kWidth = 55;
+ const int kHeight = 333;
+
+ // Transformations that we know are unsupported:
+ {
+ bitmap.setConfig(unsupportedConfig, kWidth, kHeight);
+ SkBitmapTransformer transformer = SkBitmapTransformer(bitmap, supportedPixelFormat);
+ REPORTER_ASSERT(fReporter, !transformer.isValid());
+ }
+
+ // Valid transformations:
+ {
+ // Bytes we expect to get:
+ const int kWidth = 3;
+ const int kHeight = 5;
+ const unsigned char comparisonBuffer[] = {
+ // kHeight rows, each with kWidth pixels, premultiplied ARGB for each pixel
+ 0xff,0xff,0x00,0x00, 0xff,0xff,0x00,0x00, 0xff,0xff,0x00,0x00, // red
+ 0xff,0x00,0xff,0x00, 0xff,0x00,0xff,0x00, 0xff,0x00,0xff,0x00, // green
+ 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, // blue
+ 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, // blue
+ 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, // blue
+ };
+
+ // A bitmap that should generate the above bytes:
+ bitmap.setConfig(supportedConfig, kWidth, kHeight);
+ REPORTER_ASSERT(fReporter, bitmap.allocPixels());
+ bitmap.setIsOpaque(true);
+ bitmap.eraseColor(SK_ColorBLUE);
+ bitmap.lockPixels();
+ // Change rows [0,1] from blue to [red,green].
+ SkColor oldColor = SK_ColorBLUE;
+ SkColor newColors[] = {SK_ColorRED, SK_ColorGREEN};
+ for (int y = 0; y <= 1; y++) {
+ for (int x = 0; x < kWidth; x++) {
+ REPORTER_ASSERT(fReporter, bitmap.getColor(x, y) == oldColor);
+ SkPMColor* pixel = static_cast<SkPMColor *>(bitmap.getAddr(x, y));
+ *pixel = SkPreMultiplyColor(newColors[y]);
+ REPORTER_ASSERT(fReporter, bitmap.getColor(x, y) == newColors[y]);
+ }
+ }
+ bitmap.unlockPixels();
+
+ // Transform the bitmap and confirm we got the expected results.
+ SkBitmapTransformer transformer = SkBitmapTransformer(bitmap, supportedPixelFormat);
+ REPORTER_ASSERT(fReporter, transformer.isValid());
+ REPORTER_ASSERT(fReporter, transformer.bytesNeededPerRow() == kWidth * 4);
+ REPORTER_ASSERT(fReporter, transformer.bytesNeededTotal() == kWidth * kHeight * 4);
+ int bufferSize = transformer.bytesNeededTotal();
+ SkAutoMalloc pixelBufferManager(bufferSize);
+ char *pixelBuffer = static_cast<char *>(pixelBufferManager.get());
+ REPORTER_ASSERT(fReporter,
+ transformer.copyBitmapToPixelBuffer(pixelBuffer, bufferSize));
+ REPORTER_ASSERT(fReporter, bufferSize == sizeof(comparisonBuffer));
+ REPORTER_ASSERT(fReporter, memcmp(pixelBuffer, comparisonBuffer, bufferSize) == 0);
+ }
+
+ }
+
+ Reporter* fReporter;
+ };
+
+ static TestRegistry gReg(BitmapTransformerTestClass::Factory);
+}
diff --git a/tests/CanvasTest.cpp b/tests/CanvasTest.cpp
index 657045081a..8c64d58e0d 100644
--- a/tests/CanvasTest.cpp
+++ b/tests/CanvasTest.cpp
@@ -50,6 +50,8 @@
#include "SkDevice.h"
#include "SkMatrix.h"
#include "SkNWayCanvas.h"
+#include "SkPDFDevice.h"
+#include "SkPDFDocument.h"
#include "SkPaint.h"
#include "SkPath.h"
#include "SkPicture.h"
@@ -141,6 +143,8 @@ static const char* const kNWayIndirect1StateAssertMessageFormat =
"test step %s, SkNWayCanvas indirect canvas 1 state consistency";
static const char* const kNWayIndirect2StateAssertMessageFormat =
"test step %s, SkNWayCanvas indirect canvas 2 state consistency";
+static const char* const kPdfAssertMessageFormat =
+ "PDF sanity check failed %s";
static void createBitmap(SkBitmap* bm, SkBitmap::Config config, SkColor color) {
bm->setConfig(config, kWidth, kHeight);
@@ -156,9 +160,10 @@ static SkTDArray<CanvasTestStep*>& testStepArray() {
class CanvasTestStep {
public:
- CanvasTestStep() {
+ CanvasTestStep(bool fEnablePdfTesting = true) {
*testStepArray().append() = this;
fAssertMessageFormat = kDefaultAssertMessageFormat;
+ this->fEnablePdfTesting = fEnablePdfTesting;
}
virtual ~CanvasTestStep() { }
@@ -174,9 +179,12 @@ public:
fAssertMessageFormat = format;
}
+ bool enablePdfTesting() { return fEnablePdfTesting; }
+
private:
SkString fAssertMessage;
const char* fAssertMessageFormat;
+ bool fEnablePdfTesting;
};
///////////////////////////////////////////////////////////////////////////////
@@ -250,6 +258,17 @@ public: \
}; \
static NAME##_TestStep NAME##_TestStepInstance;
+#define TEST_STEP_NO_PDF(NAME, FUNCTION) \
+class NAME##_TestStep : public CanvasTestStep{ \
+public: \
+ NAME##_TestStep() : CanvasTestStep(false) {} \
+ virtual void draw(SkCanvas* canvas, skiatest::Reporter* reporter) { \
+ FUNCTION (canvas, reporter, this); \
+ } \
+ virtual const char* name() const {return #NAME ;} \
+}; \
+static NAME##_TestStep NAME##_TestStepInstance;
+
#define SIMPLE_TEST_STEP(NAME, CALL) \
static void NAME##TestStep(SkCanvas* canvas, skiatest::Reporter*, \
CanvasTestStep*) { \
@@ -460,7 +479,8 @@ static void DrawVerticesShaderTestStep(SkCanvas* canvas,
canvas->drawVertices(SkCanvas::kTriangleFan_VertexMode, 4, pts, pts,
NULL, NULL, NULL, 0, paint);
}
-TEST_STEP(DrawVerticesShader, DrawVerticesShaderTestStep);
+// NYI: issue 240.
+TEST_STEP_NO_PDF(DrawVerticesShader, DrawVerticesShaderTestStep);
static void DrawPictureTestStep(SkCanvas* canvas,
skiatest::Reporter* reporter,
@@ -732,6 +752,19 @@ public:
}
};
+static void TestPdfDevice(skiatest::Reporter* reporter,
+ CanvasTestStep* testStep) {
+ SkISize pageSize = SkISize::Make(kWidth, kHeight);
+ SkPDFDevice device(pageSize, pageSize, SkMatrix::I());
+ SkCanvas canvas(&device);
+ testStep->setAssertMessageFormat(kPdfAssertMessageFormat);
+ testStep->draw(&canvas, reporter);
+ SkPDFDocument doc;
+ doc.appendPage(&device);
+ SkDynamicMemoryWStream stream;
+ doc.emitPDF(&stream);
+}
+
// The following class groups static functions that need to access
// the privates members of SkDeferredCanvas
class SkDeferredCanvasTester {
@@ -886,6 +919,9 @@ static void TestCanvas(skiatest::Reporter* reporter) {
TestOverrideStateConsistency(reporter, testStepArray()[testStep]);
SkPictureTester::TestPictureFlattenedObjectReuse(reporter,
testStepArray()[testStep], 0);
+ if (testStepArray()[testStep]->enablePdfTesting()) {
+ TestPdfDevice(reporter, testStepArray()[testStep]);
+ }
}
// Explicitly call reset(), so we don't leak the pixels (since kTestBitmap is a global)
diff --git a/tests/ChecksumTest.cpp b/tests/ChecksumTest.cpp
new file mode 100644
index 0000000000..03194907f5
--- /dev/null
+++ b/tests/ChecksumTest.cpp
@@ -0,0 +1,185 @@
+
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "Test.h"
+
+#include "SkBitmap.h"
+#include "SkBitmapChecksummer.h"
+#include "SkChecksum.h"
+#include "SkCityHash.h"
+#include "SkColor.h"
+
+// Word size that is large enough to hold results of any checksum type.
+typedef uint64_t checksum_result;
+
+namespace skiatest {
+ class ChecksumTestClass : public Test {
+ public:
+ static Test* Factory(void*) {return SkNEW(ChecksumTestClass); }
+ protected:
+ virtual void onGetName(SkString* name) { name->set("Checksum"); }
+ virtual void onRun(Reporter* reporter) {
+ this->fReporter = reporter;
+ RunTest();
+ }
+ private:
+ enum Algorithm {
+ kSkChecksum,
+ kSkCityHash32,
+ kSkCityHash64
+ };
+
+ // Call Compute(data, size) on the appropriate checksum algorithm,
+ // depending on this->fWhichAlgorithm.
+ checksum_result ComputeChecksum(const char *data, size_t size) {
+ switch(fWhichAlgorithm) {
+ case kSkChecksum:
+ REPORTER_ASSERT_MESSAGE(fReporter,
+ reinterpret_cast<uintptr_t>(data) % 4 == 0,
+ "test data pointer is not 32-bit aligned");
+ REPORTER_ASSERT_MESSAGE(fReporter, SkIsAlign4(size),
+ "test data size is not 32-bit aligned");
+ return SkChecksum::Compute(reinterpret_cast<const uint32_t *>(data), size);
+ case kSkCityHash32:
+ return SkCityHash::Compute32(data, size);
+ case kSkCityHash64:
+ return SkCityHash::Compute64(data, size);
+ default:
+ SkString message("fWhichAlgorithm has unknown value ");
+ message.appendf("%d", fWhichAlgorithm);
+ fReporter->reportFailed(message);
+ }
+ // we never get here
+ return 0;
+ }
+
+ // Confirm that the checksum algorithm (specified by fWhichAlgorithm)
+ // generates the same results if called twice over the same data.
+ void TestChecksumSelfConsistency(size_t buf_size) {
+ SkAutoMalloc storage(buf_size);
+ char* ptr = reinterpret_cast<char *>(storage.get());
+
+ REPORTER_ASSERT(fReporter,
+ GetTestDataChecksum(8, 0) ==
+ GetTestDataChecksum(8, 0));
+ REPORTER_ASSERT(fReporter,
+ GetTestDataChecksum(8, 0) !=
+ GetTestDataChecksum(8, 1));
+
+ sk_bzero(ptr, buf_size);
+ checksum_result prev = 0;
+
+ // assert that as we change values (from 0 to non-zero) in
+ // our buffer, we get a different value
+ for (size_t i = 0; i < buf_size; ++i) {
+ ptr[i] = (i & 0x7f) + 1; // need some non-zero value here
+
+ // Try checksums of different-sized chunks, but always
+ // 32-bit aligned and big enough to contain all the
+ // nonzero bytes. (Remaining bytes will still be zero
+ // from the initial sk_bzero() call.)
+ size_t checksum_size = (((i/4)+1)*4);
+ REPORTER_ASSERT(fReporter, checksum_size <= buf_size);
+
+ checksum_result curr = ComputeChecksum(ptr, checksum_size);
+ REPORTER_ASSERT(fReporter, prev != curr);
+ checksum_result again = ComputeChecksum(ptr, checksum_size);
+ REPORTER_ASSERT(fReporter, again == curr);
+ prev = curr;
+ }
+ }
+
+ // Return the checksum of a buffer of bytes 'len' long.
+ // The pattern of values within the buffer will be consistent
+ // for every call, based on 'seed'.
+ checksum_result GetTestDataChecksum(size_t len, char seed=0) {
+ SkAutoMalloc storage(len);
+ char* start = reinterpret_cast<char *>(storage.get());
+ char* ptr = start;
+ for (size_t i = 0; i < len; ++i) {
+ *ptr++ = ((seed+i) & 0x7f);
+ }
+ checksum_result result = ComputeChecksum(start, len);
+ return result;
+ }
+
+ // Fill in bitmap with test data.
+ void CreateTestBitmap(SkBitmap &bitmap, SkBitmap::Config config, int width, int height,
+ SkColor color) {
+ bitmap.setConfig(config, width, height);
+ REPORTER_ASSERT(fReporter, bitmap.allocPixels());
+ bitmap.setIsOpaque(true);
+ bitmap.eraseColor(color);
+ }
+
+ void RunTest() {
+ // Test self-consistency of checksum algorithms.
+ fWhichAlgorithm = kSkChecksum;
+ TestChecksumSelfConsistency(128);
+ fWhichAlgorithm = kSkCityHash32;
+ TestChecksumSelfConsistency(128);
+ fWhichAlgorithm = kSkCityHash64;
+ TestChecksumSelfConsistency(128);
+
+ // Test checksum results that should be consistent across
+ // versions and platforms.
+ fWhichAlgorithm = kSkChecksum;
+ REPORTER_ASSERT(fReporter, ComputeChecksum(NULL, 0) == 0);
+ fWhichAlgorithm = kSkCityHash32;
+ REPORTER_ASSERT(fReporter, ComputeChecksum(NULL, 0) == 0xdc56d17a);
+ REPORTER_ASSERT(fReporter, GetTestDataChecksum(4) == 0x616e1132);
+ REPORTER_ASSERT(fReporter, GetTestDataChecksum(8) == 0xeb0fd2d6);
+ REPORTER_ASSERT(fReporter, GetTestDataChecksum(128) == 0x5321e430);
+ REPORTER_ASSERT(fReporter, GetTestDataChecksum(132) == 0x924a10e4);
+ REPORTER_ASSERT(fReporter, GetTestDataChecksum(256) == 0xd4de9dc9);
+ REPORTER_ASSERT(fReporter, GetTestDataChecksum(260) == 0xecf0325d);
+ fWhichAlgorithm = kSkCityHash64;
+ REPORTER_ASSERT(fReporter, ComputeChecksum(NULL, 0) == 0x9ae16a3b2f90404fULL);
+ REPORTER_ASSERT(fReporter, GetTestDataChecksum(4) == 0x82bffd898958e540ULL);
+ REPORTER_ASSERT(fReporter, GetTestDataChecksum(8) == 0xad5a13e1e8e93b98ULL);
+ REPORTER_ASSERT(fReporter, GetTestDataChecksum(128) == 0x10b153630af1f395ULL);
+ REPORTER_ASSERT(fReporter, GetTestDataChecksum(132) == 0x7db71dc4adcc6647ULL);
+ REPORTER_ASSERT(fReporter, GetTestDataChecksum(256) == 0xeee763519b91b010ULL);
+ REPORTER_ASSERT(fReporter, GetTestDataChecksum(260) == 0x2fe19e0b2239bc23ULL);
+
+ // TODO: note the weakness exposed by these collisions...
+ // We need to improve the SkChecksum algorithm.
+ // We would prefer that these asserts FAIL!
+ // Filed as https://code.google.com/p/skia/issues/detail?id=981
+ // ('SkChecksum algorithm allows for way too many collisions')
+ fWhichAlgorithm = kSkChecksum;
+ REPORTER_ASSERT(fReporter,
+ GetTestDataChecksum(128) == GetTestDataChecksum(256));
+ REPORTER_ASSERT(fReporter,
+ GetTestDataChecksum(132) == GetTestDataChecksum(260));
+
+ // Test SkBitmapChecksummer
+ SkBitmap bitmap;
+ // initial test case
+ CreateTestBitmap(bitmap, SkBitmap::kARGB_8888_Config, 333, 555, SK_ColorBLUE);
+ REPORTER_ASSERT(fReporter,
+ SkBitmapChecksummer::Compute64(bitmap) == 0x18f9df68b1b02f38ULL);
+ // same pixel data but different dimensions should yield a different checksum
+ CreateTestBitmap(bitmap, SkBitmap::kARGB_8888_Config, 555, 333, SK_ColorBLUE);
+ REPORTER_ASSERT(fReporter,
+ SkBitmapChecksummer::Compute64(bitmap) == 0x6b0298183f786c8eULL);
+ // same dimensions but different color should yield a different checksum
+ CreateTestBitmap(bitmap, SkBitmap::kARGB_8888_Config, 555, 333, SK_ColorGREEN);
+ REPORTER_ASSERT(fReporter,
+ SkBitmapChecksummer::Compute64(bitmap) == 0xc6b4b3f6fadaaf37ULL);
+ // same pixel colors in a different config should yield the same checksum
+ CreateTestBitmap(bitmap, SkBitmap::kARGB_4444_Config, 555, 333, SK_ColorGREEN);
+ REPORTER_ASSERT(fReporter,
+ SkBitmapChecksummer::Compute64(bitmap) == 0xc6b4b3f6fadaaf37ULL);
+ }
+
+ Reporter* fReporter;
+ Algorithm fWhichAlgorithm;
+ };
+
+ static TestRegistry gReg(ChecksumTestClass::Factory);
+}
diff --git a/tests/ClipCacheTest.cpp b/tests/ClipCacheTest.cpp
index ec8169eff4..1aeebb80dc 100644
--- a/tests/ClipCacheTest.cpp
+++ b/tests/ClipCacheTest.cpp
@@ -103,8 +103,7 @@ static void check_state(skiatest::Reporter* reporter,
GrTexture* mask,
const GrIRect& bound) {
SkClipStack cacheClip;
- cache.getLastClip(&cacheClip);
- REPORTER_ASSERT(reporter, clip == cacheClip);
+ REPORTER_ASSERT(reporter, clip.getTopmostGenID() == cache.getLastClipGenID());
REPORTER_ASSERT(reporter, mask == cache.getLastMask());
@@ -146,7 +145,7 @@ static void test_cache(skiatest::Reporter* reporter, GrContext* context) {
desc.fHeight = Y_SIZE;
desc.fConfig = kSkia8888_PM_GrPixelConfig;
- cache.acquireMask(clip1, desc, bound1);
+ cache.acquireMask(clip1.getTopmostGenID(), desc, bound1);
GrTexture* texture1 = cache.getLastMask();
REPORTER_ASSERT(reporter, texture1);
@@ -171,7 +170,7 @@ static void test_cache(skiatest::Reporter* reporter, GrContext* context) {
SkClipStack clip2(bound2);
- cache.acquireMask(clip2, desc, bound2);
+ cache.acquireMask(clip2.getTopmostGenID(), desc, bound2);
GrTexture* texture2 = cache.getLastMask();
REPORTER_ASSERT(reporter, texture2);
@@ -185,8 +184,8 @@ static void test_cache(skiatest::Reporter* reporter, GrContext* context) {
REPORTER_ASSERT(reporter, 1 == texture2->getRefCnt());
// check to make sure canReuse works
- REPORTER_ASSERT(reporter, cache.canReuse(clip2, bound2));
- REPORTER_ASSERT(reporter, !cache.canReuse(clip1, bound1));
+ REPORTER_ASSERT(reporter, cache.canReuse(clip2.getTopmostGenID(), bound2));
+ REPORTER_ASSERT(reporter, !cache.canReuse(clip1.getTopmostGenID(), bound1));
// pop the state
cache.pop();
diff --git a/tests/ClipCubicTest.cpp b/tests/ClipCubicTest.cpp
index 2e913a7354..e980781d28 100644
--- a/tests/ClipCubicTest.cpp
+++ b/tests/ClipCubicTest.cpp
@@ -20,7 +20,7 @@ static void test_giantClip() {
bm.setConfig(SkBitmap::kARGB_8888_Config, 64919, 1);
bm.allocPixels();
SkCanvas canvas(bm);
- canvas.clear(0);
+ canvas.clear(SK_ColorTRANSPARENT);
SkPath path;
path.moveTo(0, 0); path.lineTo(1, 0); path.lineTo(33, 1);
diff --git a/tests/ClipStackTest.cpp b/tests/ClipStackTest.cpp
index 8a74dc8712..d4391d7af6 100644
--- a/tests/ClipStackTest.cpp
+++ b/tests/ClipStackTest.cpp
@@ -6,10 +6,14 @@
* found in the LICENSE file.
*/
#include "Test.h"
+#if SK_SUPPORT_GPU
+ #include "GrReducedClip.h"
+#endif
#include "SkClipStack.h"
#include "SkPath.h"
+#include "SkRandom.h"
#include "SkRect.h"
-
+#include "SkRegion.h"
static void test_assign_and_comparison(skiatest::Reporter* reporter) {
@@ -148,13 +152,14 @@ static void test_iterators(skiatest::Reporter* reporter) {
// bottom to top iteration
{
- const SkClipStack::B2TIter::Clip* clip = NULL;
+ const SkClipStack::Element* element = NULL;
SkClipStack::B2TIter iter(stack);
int i;
- for (i = 0, clip = iter.next(); clip; ++i, clip = iter.next()) {
- REPORTER_ASSERT(reporter, *clip->fRect == gRects[i]);
+ for (i = 0, element = iter.next(); element; ++i, element = iter.next()) {
+ REPORTER_ASSERT(reporter, SkClipStack::Element::kRect_Type == element->getType());
+ REPORTER_ASSERT(reporter, element->getRect() == gRects[i]);
}
SkASSERT(i == 4);
@@ -162,13 +167,14 @@ static void test_iterators(skiatest::Reporter* reporter) {
// top to bottom iteration
{
- const SkClipStack::Iter::Clip* clip = NULL;
+ const SkClipStack::Element* element = NULL;
SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
int i;
- for (i = 3, clip = iter.prev(); clip; --i, clip = iter.prev()) {
- REPORTER_ASSERT(reporter, *clip->fRect == gRects[i]);
+ for (i = 3, element = iter.prev(); element; --i, element = iter.prev()) {
+ REPORTER_ASSERT(reporter, SkClipStack::Element::kRect_Type == element->getType());
+ REPORTER_ASSERT(reporter, element->getRect() == gRects[i]);
}
SkASSERT(i == -1);
@@ -176,12 +182,13 @@ static void test_iterators(skiatest::Reporter* reporter) {
// skipToTopmost
{
- const SkClipStack::Iter::Clip*clip = NULL;
+ const SkClipStack::Element* element = NULL;
SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart);
- clip = iter.skipToTopmost(SkRegion::kUnion_Op);
- REPORTER_ASSERT(reporter, *clip->fRect == gRects[3]);
+ element = iter.skipToTopmost(SkRegion::kUnion_Op);
+ REPORTER_ASSERT(reporter, SkClipStack::Element::kRect_Type == element->getType());
+ REPORTER_ASSERT(reporter, element->getRect() == gRects[3]);
}
}
@@ -358,16 +365,33 @@ static int count(const SkClipStack& stack) {
SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
- const SkClipStack::Iter::Clip* clip = NULL;
+ const SkClipStack::Element* element = NULL;
int count = 0;
- for (clip = iter.prev(); clip; clip = iter.prev(), ++count) {
+ for (element = iter.prev(); element; element = iter.prev(), ++count) {
;
}
return count;
}
+static void test_rect_inverse_fill(skiatest::Reporter* reporter) {
+ // non-intersecting rectangles
+ SkRect rect = SkRect::MakeLTRB(0, 0, 10, 10);
+
+ SkPath path;
+ path.addRect(rect);
+ path.toggleInverseFillType();
+ SkClipStack stack;
+ stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
+
+ SkRect bounds;
+ SkClipStack::BoundsType boundsType;
+ stack.getBounds(&bounds, &boundsType);
+ REPORTER_ASSERT(reporter, SkClipStack::kInsideOut_BoundsType == boundsType);
+ REPORTER_ASSERT(reporter, bounds == rect);
+}
+
// Test out SkClipStack's merging of rect clips. In particular exercise
// merging of aa vs. bw rects.
static void test_rect_merging(skiatest::Reporter* reporter) {
@@ -473,6 +497,420 @@ static void test_rect_merging(skiatest::Reporter* reporter) {
}
}
+static void test_quickContains(skiatest::Reporter* reporter) {
+ SkRect testRect = SkRect::MakeLTRB(10, 10, 40, 40);
+ SkRect insideRect = SkRect::MakeLTRB(20, 20, 30, 30);
+ SkRect intersectingRect = SkRect::MakeLTRB(25, 25, 50, 50);
+ SkRect outsideRect = SkRect::MakeLTRB(0, 0, 50, 50);
+ SkRect nonIntersectingRect = SkRect::MakeLTRB(100, 100, 110, 110);
+
+ SkPath insideCircle;
+ insideCircle.addCircle(25, 25, 5);
+ SkPath intersectingCircle;
+ intersectingCircle.addCircle(25, 40, 10);
+ SkPath outsideCircle;
+ outsideCircle.addCircle(25, 25, 50);
+ SkPath nonIntersectingCircle;
+ nonIntersectingCircle.addCircle(100, 100, 5);
+
+ {
+ SkClipStack stack;
+ stack.clipDevRect(outsideRect, SkRegion::kDifference_Op, false);
+ // return false because quickContains currently does not care for kDifference_Op
+ REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+ }
+
+ // Replace Op tests
+ {
+ SkClipStack stack;
+ stack.clipDevRect(outsideRect, SkRegion::kReplace_Op, false);
+ REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
+ }
+
+ {
+ SkClipStack stack;
+ stack.clipDevRect(insideRect, SkRegion::kIntersect_Op, false);
+ stack.save(); // To prevent in-place substitution by replace OP
+ stack.clipDevRect(outsideRect, SkRegion::kReplace_Op, false);
+ REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
+ stack.restore();
+ }
+
+ {
+ SkClipStack stack;
+ stack.clipDevRect(outsideRect, SkRegion::kIntersect_Op, false);
+ stack.save(); // To prevent in-place substitution by replace OP
+ stack.clipDevRect(insideRect, SkRegion::kReplace_Op, false);
+ REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+ stack.restore();
+ }
+
+ // Verify proper traversal of multi-element clip
+ {
+ SkClipStack stack;
+ stack.clipDevRect(insideRect, SkRegion::kIntersect_Op, false);
+ // Use a path for second clip to prevent in-place intersection
+ stack.clipDevPath(outsideCircle, SkRegion::kIntersect_Op, false);
+ REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+ }
+
+ // Intersect Op tests with rectangles
+ {
+ SkClipStack stack;
+ stack.clipDevRect(outsideRect, SkRegion::kIntersect_Op, false);
+ REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
+ }
+
+ {
+ SkClipStack stack;
+ stack.clipDevRect(insideRect, SkRegion::kIntersect_Op, false);
+ REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+ }
+
+ {
+ SkClipStack stack;
+ stack.clipDevRect(intersectingRect, SkRegion::kIntersect_Op, false);
+ REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+ }
+
+ {
+ SkClipStack stack;
+ stack.clipDevRect(nonIntersectingRect, SkRegion::kIntersect_Op, false);
+ REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+ }
+
+ // Intersect Op tests with circle paths
+ {
+ SkClipStack stack;
+ stack.clipDevPath(outsideCircle, SkRegion::kIntersect_Op, false);
+ REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
+ }
+
+ {
+ SkClipStack stack;
+ stack.clipDevPath(insideCircle, SkRegion::kIntersect_Op, false);
+ REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+ }
+
+ {
+ SkClipStack stack;
+ stack.clipDevPath(intersectingCircle, SkRegion::kIntersect_Op, false);
+ REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+ }
+
+ {
+ SkClipStack stack;
+ stack.clipDevPath(nonIntersectingCircle, SkRegion::kIntersect_Op, false);
+ REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+ }
+
+ // Intersect Op tests with inverse filled rectangles
+ {
+ SkClipStack stack;
+ SkPath path;
+ path.addRect(outsideRect);
+ path.toggleInverseFillType();
+ stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
+ REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+ }
+
+ {
+ SkClipStack stack;
+ SkPath path;
+ path.addRect(insideRect);
+ path.toggleInverseFillType();
+ stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
+ REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+ }
+
+ {
+ SkClipStack stack;
+ SkPath path;
+ path.addRect(intersectingRect);
+ path.toggleInverseFillType();
+ stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
+ REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+ }
+
+ {
+ SkClipStack stack;
+ SkPath path;
+ path.addRect(nonIntersectingRect);
+ path.toggleInverseFillType();
+ stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
+ REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
+ }
+
+ // Intersect Op tests with inverse filled circles
+ {
+ SkClipStack stack;
+ SkPath path = outsideCircle;
+ path.toggleInverseFillType();
+ stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
+ REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+ }
+
+ {
+ SkClipStack stack;
+ SkPath path = insideCircle;
+ path.toggleInverseFillType();
+ stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
+ REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+ }
+
+ {
+ SkClipStack stack;
+ SkPath path = intersectingCircle;
+ path.toggleInverseFillType();
+ stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
+ REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+ }
+
+ {
+ SkClipStack stack;
+ SkPath path = nonIntersectingCircle;
+ path.toggleInverseFillType();
+ stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
+ REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+// Functions that add a shape to the clip stack. The shape is computed from a rectangle.
+// AA is always disabled since the clip stack reducer can cause changes in aa rasterization of the
+// stack. A fractional edge repeated in different elements may be rasterized fewer times using the
+// reduced stack.
+typedef void (*AddElementFunc) (const SkRect& rect,
+ bool invert,
+ SkRegion::Op op,
+ SkClipStack* stack);
+
+static void add_round_rect(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) {
+ SkPath path;
+ SkScalar rx = rect.width() / 10;
+ SkScalar ry = rect.height() / 20;
+ path.addRoundRect(rect, rx, ry);
+ if (invert) {
+ path.setFillType(SkPath::kInverseWinding_FillType);
+ }
+ stack->clipDevPath(path, op, false);
+};
+
+static void add_rect(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) {
+ if (invert) {
+ SkPath path;
+ path.addRect(rect);
+ path.setFillType(SkPath::kInverseWinding_FillType);
+ stack->clipDevPath(path, op, false);
+ } else {
+ stack->clipDevRect(rect, op, false);
+ }
+};
+
+static void add_oval(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) {
+ SkPath path;
+ path.addOval(rect);
+ if (invert) {
+ path.setFillType(SkPath::kInverseWinding_FillType);
+ }
+ stack->clipDevPath(path, op, false);
+};
+
+static void add_elem_to_stack(const SkClipStack::Element& element, SkClipStack* stack) {
+ switch (element.getType()) {
+ case SkClipStack::Element::kRect_Type:
+ stack->clipDevRect(element.getRect(), element.getOp(), element.isAA());
+ break;
+ case SkClipStack::Element::kPath_Type:
+ stack->clipDevPath(element.getPath(), element.getOp(), element.isAA());
+ break;
+ case SkClipStack::Element::kEmpty_Type:
+ SkDEBUGFAIL("Why did the reducer produce an explicit empty.");
+ stack->clipEmpty();
+ break;
+ }
+}
+
+static void add_elem_to_region(const SkClipStack::Element& element,
+ const SkIRect& bounds,
+ SkRegion* region) {
+ SkRegion elemRegion;
+ SkRegion boundsRgn(bounds);
+
+ switch (element.getType()) {
+ case SkClipStack::Element::kRect_Type: {
+ SkPath path;
+ path.addRect(element.getRect());
+ elemRegion.setPath(path, boundsRgn);
+ break;
+ }
+ case SkClipStack::Element::kPath_Type:
+ elemRegion.setPath(element.getPath(), boundsRgn);
+ break;
+ case SkClipStack::Element::kEmpty_Type:
+ //
+ region->setEmpty();
+ return;
+ }
+ region->op(elemRegion, element.getOp());
+}
+
+// This can assist with debugging the clip stack reduction code when the test below fails.
+static void print_clip(const SkClipStack::Element& element) {
+ static const char* kOpStrs[] = {
+ "DF",
+ "IS",
+ "UN",
+ "XR",
+ "RD",
+ "RP",
+ };
+ if (SkClipStack::Element::kEmpty_Type != element.getType()) {
+ const SkRect& bounds = element.getBounds();
+ bool isRect = SkClipStack::Element::kRect_Type == element.getType();
+ SkDebugf("%s %s %s [%f %f] x [%f %f]\n",
+ kOpStrs[element.getOp()],
+ (isRect ? "R" : "P"),
+ (element.isInverseFilled() ? "I" : " "),
+ bounds.fLeft, bounds.fRight, bounds.fTop, bounds.fBottom);
+ } else {
+ SkDebugf("EM\n");
+ }
+}
+
+static void test_reduced_clip_stack(skiatest::Reporter* reporter) {
+ // We construct random clip stacks, reduce them, and then rasterize both versions to verify that
+ // they are equal.
+
+ // All the clip elements will be contained within these bounds.
+ static const SkRect kBounds = SkRect::MakeWH(100, 100);
+
+ enum {
+ kNumTests = 200,
+ kMinElemsPerTest = 1,
+ kMaxElemsPerTest = 50,
+ };
+
+ // min/max size of a clip element as a fraction of kBounds.
+ static const SkScalar kMinElemSizeFrac = SK_Scalar1 / 5;
+ static const SkScalar kMaxElemSizeFrac = SK_Scalar1;
+
+ static const SkRegion::Op kOps[] = {
+ SkRegion::kDifference_Op,
+ SkRegion::kIntersect_Op,
+ SkRegion::kUnion_Op,
+ SkRegion::kXOR_Op,
+ SkRegion::kReverseDifference_Op,
+ SkRegion::kReplace_Op,
+ };
+
+ // Replace operations short-circuit the optimizer. We want to make sure that we test this code
+ // path a little bit but we don't want it to prevent us from testing many longer traversals in
+ // the optimizer.
+ static const int kReplaceDiv = 4 * kMaxElemsPerTest;
+
+ // We want to test inverse fills. However, they are quite rare in practice so don't over do it.
+ static const SkScalar kFractionInverted = SK_Scalar1 / kMaxElemsPerTest;
+
+ static const AddElementFunc kElementFuncs[] = {
+ add_rect,
+ add_round_rect,
+ add_oval,
+ };
+
+ SkRandom r;
+
+ for (int i = 0; i < kNumTests; ++i) {
+ // Randomly generate a clip stack.
+ SkClipStack stack;
+ int numElems = r.nextRangeU(kMinElemsPerTest, kMaxElemsPerTest);
+ for (int e = 0; e < numElems; ++e) {
+ SkRegion::Op op = kOps[r.nextULessThan(SK_ARRAY_COUNT(kOps))];
+ if (op == SkRegion::kReplace_Op) {
+ if (r.nextU() % kReplaceDiv) {
+ --e;
+ continue;
+ }
+ }
+
+ // saves can change the clip stack behavior when an element is added.
+ bool doSave = r.nextBool();
+
+ SkSize size = SkSize::Make(
+ SkScalarFloorToScalar(SkScalarMul(kBounds.width(), r.nextRangeScalar(kMinElemSizeFrac, kMaxElemSizeFrac))),
+ SkScalarFloorToScalar(SkScalarMul(kBounds.height(), r.nextRangeScalar(kMinElemSizeFrac, kMaxElemSizeFrac))));
+
+ SkPoint xy = {SkScalarFloorToScalar(r.nextRangeScalar(kBounds.fLeft, kBounds.fRight - size.fWidth)),
+ SkScalarFloorToScalar(r.nextRangeScalar(kBounds.fTop, kBounds.fBottom - size.fHeight))};
+
+ SkRect rect = SkRect::MakeXYWH(xy.fX, xy.fY, size.fWidth, size.fHeight);
+
+ bool invert = r.nextBiasedBool(kFractionInverted);
+ kElementFuncs[r.nextULessThan(SK_ARRAY_COUNT(kElementFuncs))](rect, invert, op, &stack);
+ if (doSave) {
+ stack.save();
+ }
+ }
+
+ SkRect inflatedBounds = kBounds;
+ inflatedBounds.outset(kBounds.width() / 2, kBounds.height() / 2);
+ SkIRect inflatedIBounds;
+ inflatedBounds.roundOut(&inflatedIBounds);
+
+ typedef GrReducedClip::ElementList ElementList;
+ // Get the reduced version of the stack.
+ ElementList reducedClips;
+
+ GrReducedClip::InitialState initial;
+ SkIRect tBounds;
+ SkIRect* tightBounds = r.nextBool() ? &tBounds : NULL;
+ GrReducedClip::ReduceClipStack(stack,
+ inflatedIBounds,
+ &reducedClips,
+ &initial,
+ tightBounds);
+
+ // Build a new clip stack based on the reduced clip elements
+ SkClipStack reducedStack;
+ if (GrReducedClip::kAllOut_InitialState == initial) {
+ // whether the result is bounded or not, the whole plane should start outside the clip.
+ reducedStack.clipEmpty();
+ }
+ for (ElementList::Iter iter = reducedClips.headIter(); NULL != iter.get(); iter.next()) {
+ add_elem_to_stack(*iter.get(), &reducedStack);
+ }
+
+ // GrReducedClipStack assumes that the final result is clipped to the returned bounds
+ if (NULL != tightBounds) {
+ reducedStack.clipDevRect(*tightBounds, SkRegion::kIntersect_Op);
+ }
+
+ // convert both the original stack and reduced stack to SkRegions and see if they're equal
+ SkRegion region;
+ SkRegion reducedRegion;
+
+ region.setRect(inflatedIBounds);
+ const SkClipStack::Element* element;
+ SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart);
+ while ((element = iter.next())) {
+ add_elem_to_region(*element, inflatedIBounds, &region);
+ }
+
+ reducedRegion.setRect(inflatedIBounds);
+ iter.reset(reducedStack, SkClipStack::Iter::kBottom_IterStart);
+ while ((element = iter.next())) {
+ add_elem_to_region(*element, inflatedIBounds, &reducedRegion);
+ }
+
+ REPORTER_ASSERT(reporter, region == reducedRegion);
+ }
+}
+
+#endif
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
static void TestClipStack(skiatest::Reporter* reporter) {
SkClipStack stack;
@@ -491,15 +929,14 @@ static void TestClipStack(skiatest::Reporter* reporter) {
// all of the above rects should have been intersected, leaving only 1 rect
SkClipStack::B2TIter iter(stack);
- const SkClipStack::B2TIter::Clip* clip = iter.next();
+ const SkClipStack::Element* element = iter.next();
SkRect answer;
answer.iset(25, 25, 75, 75);
- REPORTER_ASSERT(reporter, clip);
- REPORTER_ASSERT(reporter, clip->fRect);
- REPORTER_ASSERT(reporter, !clip->fPath);
- REPORTER_ASSERT(reporter, SkRegion::kIntersect_Op == clip->fOp);
- REPORTER_ASSERT(reporter, *clip->fRect == answer);
+ REPORTER_ASSERT(reporter, NULL != element);
+ REPORTER_ASSERT(reporter, SkClipStack::Element::kRect_Type == element->getType());
+ REPORTER_ASSERT(reporter, SkRegion::kIntersect_Op == element->getOp());
+ REPORTER_ASSERT(reporter, element->getRect() == answer);
// now check that we only had one in our iterator
REPORTER_ASSERT(reporter, !iter.next());
@@ -513,6 +950,11 @@ static void TestClipStack(skiatest::Reporter* reporter) {
test_bounds(reporter, false); // once with paths
test_isWideOpen(reporter);
test_rect_merging(reporter);
+ test_rect_inverse_fill(reporter);
+ test_quickContains(reporter);
+#if SK_SUPPORT_GPU
+ test_reduced_clip_stack(reporter);
+#endif
}
#include "TestClassDef.h"
diff --git a/tests/DeferredCanvasTest.cpp b/tests/DeferredCanvasTest.cpp
index 2c27971f09..1e7725d592 100644
--- a/tests/DeferredCanvasTest.cpp
+++ b/tests/DeferredCanvasTest.cpp
@@ -7,6 +7,7 @@
*/
#include "Test.h"
#include "SkBitmap.h"
+#include "SkBitmapProcShader.h"
#include "SkDeferredCanvas.h"
#include "SkDevice.h"
#include "SkShader.h"
@@ -162,6 +163,19 @@ static void TestDeferredCanvasFreshFrame(skiatest::Reporter* reporter) {
paint.setStyle(SkPaint::kFill_Style);
paint.setAlpha(255);
canvas.drawRect(fullRect, paint);
+ canvas.restore();
+ REPORTER_ASSERT(reporter, !canvas.isFreshFrame());
+ }
+ {
+ canvas.save(SkCanvas::kMatrixClip_SaveFlag);
+ SkPaint paint;
+ paint.setStyle( SkPaint::kFill_Style );
+ paint.setAlpha( 255 );
+ SkPath path;
+ path.addCircle(SkIntToScalar(0), SkIntToScalar(0), SkIntToScalar(2));
+ canvas.clipPath(path, SkRegion::kIntersect_Op, false);
+ canvas.drawRect(fullRect, paint);
+ canvas.restore();
REPORTER_ASSERT(reporter, !canvas.isFreshFrame());
}
@@ -181,7 +195,7 @@ static void TestDeferredCanvasFreshFrame(skiatest::Reporter* reporter) {
paint.setAlpha( 100 );
paint.setXfermodeMode(SkXfermode::kSrc_Mode);
canvas.drawRect(fullRect, paint);
- REPORTER_ASSERT(reporter, !canvas.isFreshFrame());
+ REPORTER_ASSERT(reporter, canvas.isFreshFrame());
}
}
@@ -348,6 +362,89 @@ static void TestDeferredCanvasSkip(skiatest::Reporter* reporter) {
}
+static void TestDeferredCanvasBitmapShaderNoLeak(skiatest::Reporter* reporter) {
+ // This is a regression test for crbug.com/155875
+ // This test covers a code path that inserts bitmaps into the bitmap heap through the
+ // flattening of SkBitmapProcShaders. The refcount in the bitmap heap is maintained through
+ // the flattening and unflattening of the shader.
+ SkBitmap store;
+ store.setConfig(SkBitmap::kARGB_8888_Config, 100, 100);
+ store.allocPixels();
+ SkDevice device(store);
+ SkDeferredCanvas canvas(&device);
+ // test will fail if nbIterations is not in sync with
+ // BITMAPS_TO_KEEP in SkGPipeWrite.cpp
+ const int nbIterations = 5;
+ size_t bytesAllocated = 0;
+ for(int pass = 0; pass < 2; ++pass) {
+ for(int i = 0; i < nbIterations; ++i) {
+ SkPaint paint;
+ SkBitmap paintPattern;
+ paintPattern.setConfig(SkBitmap::kARGB_8888_Config, 10, 10);
+ paintPattern.allocPixels();
+ paint.setShader(SkNEW_ARGS(SkBitmapProcShader,
+ (paintPattern, SkShader::kClamp_TileMode, SkShader::kClamp_TileMode)))->unref();
+ canvas.drawPaint(paint);
+ canvas.flush();
+
+ // In the first pass, memory allocation should be monotonically increasing as
+ // the bitmap heap slots fill up. In the second pass memory allocation should be
+ // stable as bitmap heap slots get recycled.
+ size_t newBytesAllocated = canvas.storageAllocatedForRecording();
+ if (pass == 0) {
+ REPORTER_ASSERT(reporter, newBytesAllocated > bytesAllocated);
+ bytesAllocated = newBytesAllocated;
+ } else {
+ REPORTER_ASSERT(reporter, newBytesAllocated == bytesAllocated);
+ }
+ }
+ }
+ // All cached resources should be evictable since last canvas call was flush()
+ canvas.freeMemoryIfPossible(~0);
+ REPORTER_ASSERT(reporter, 0 == canvas.storageAllocatedForRecording());
+}
+
+static void TestDeferredCanvasBitmapSizeThreshold(skiatest::Reporter* reporter) {
+ SkBitmap store;
+ store.setConfig(SkBitmap::kARGB_8888_Config, 100, 100);
+ store.allocPixels();
+
+ SkBitmap sourceImage;
+ // 100 by 100 image, takes 40,000 bytes in memory
+ sourceImage.setConfig(SkBitmap::kARGB_8888_Config, 100, 100);
+ sourceImage.allocPixels();
+
+ // 1 under : should not store the image
+ {
+ SkDevice device(store);
+ SkDeferredCanvas canvas(&device);
+ canvas.setBitmapSizeThreshold(39999);
+ canvas.drawBitmap(sourceImage, 0, 0, NULL);
+ size_t newBytesAllocated = canvas.storageAllocatedForRecording();
+ REPORTER_ASSERT(reporter, newBytesAllocated == 0);
+ }
+
+ // exact value : should store the image
+ {
+ SkDevice device(store);
+ SkDeferredCanvas canvas(&device);
+ canvas.setBitmapSizeThreshold(40000);
+ canvas.drawBitmap(sourceImage, 0, 0, NULL);
+ size_t newBytesAllocated = canvas.storageAllocatedForRecording();
+ REPORTER_ASSERT(reporter, newBytesAllocated > 0);
+ }
+
+ // 1 over : should still store the image
+ {
+ SkDevice device(store);
+ SkDeferredCanvas canvas(&device);
+ canvas.setBitmapSizeThreshold(40001);
+ canvas.drawBitmap(sourceImage, 0, 0, NULL);
+ size_t newBytesAllocated = canvas.storageAllocatedForRecording();
+ REPORTER_ASSERT(reporter, newBytesAllocated > 0);
+ }
+}
+
static void TestDeferredCanvas(skiatest::Reporter* reporter) {
TestDeferredCanvasBitmapAccess(reporter);
TestDeferredCanvasFlush(reporter);
@@ -355,6 +452,8 @@ static void TestDeferredCanvas(skiatest::Reporter* reporter) {
TestDeferredCanvasMemoryLimit(reporter);
TestDeferredCanvasBitmapCaching(reporter);
TestDeferredCanvasSkip(reporter);
+ TestDeferredCanvasBitmapShaderNoLeak(reporter);
+ TestDeferredCanvasBitmapSizeThreshold(reporter);
}
#include "TestClassDef.h"
diff --git a/tests/DrawBitmapRectTest.cpp b/tests/DrawBitmapRectTest.cpp
index 0147a71faf..f74199efa3 100644
--- a/tests/DrawBitmapRectTest.cpp
+++ b/tests/DrawBitmapRectTest.cpp
@@ -36,7 +36,7 @@ static void test_wacky_bitmapshader(skiatest::Reporter* reporter,
SkBitmap dev;
dev.setConfig(SkBitmap::kARGB_8888_Config, 0x56F, 0x4f6);
dev.allocPixels();
- dev.eraseColor(0); // necessary, so we know if we draw to it
+ dev.eraseColor(SK_ColorTRANSPARENT); // necessary, so we know if we draw to it
SkMatrix matrix;
diff --git a/tests/DrawPathTest.cpp b/tests/DrawPathTest.cpp
index 55e3164b10..e8cfa54d36 100644
--- a/tests/DrawPathTest.cpp
+++ b/tests/DrawPathTest.cpp
@@ -210,7 +210,7 @@ static void test_giantaa(skiatest::Reporter* reporter) {
const int W = 400;
const int H = 400;
SkAutoTUnref<SkCanvas> canvas(new_canvas(33000, 10));
- canvas.get()->clear(0);
+ canvas.get()->clear(SK_ColorTRANSPARENT);
SkPaint paint;
paint.setAntiAlias(true);
@@ -219,6 +219,48 @@ static void test_giantaa(skiatest::Reporter* reporter) {
canvas.get()->drawPath(path, paint);
}
+// Extremely large path_length/dash_length ratios may cause infinite looping
+// in SkDashPathEffect::filterPath() due to single precision rounding.
+// The test is quite expensive, but it should get much faster after the fix
+// for http://crbug.com/165432 goes in.
+static void test_infinite_dash(skiatest::Reporter* reporter) {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.lineTo(5000000, 0);
+
+ SkScalar intervals[] = { 0.2f, 0.2f };
+ SkDashPathEffect dash(intervals, 2, 0);
+
+ SkPath filteredPath;
+ SkPaint paint;
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setPathEffect(&dash);
+
+ paint.getFillPath(path, &filteredPath);
+ // If we reach this, we passed.
+ REPORTER_ASSERT(reporter, true);
+}
+
+// http://crbug.com/165432
+// Limit extreme dash path effects to avoid exhausting the system memory.
+static void test_crbug_165432(skiatest::Reporter* reporter) {
+ SkPath path;
+ path.moveTo(0, 0);
+ path.lineTo(10000000, 0);
+
+ SkScalar intervals[] = { 0.5f, 0.5f };
+ SkDashPathEffect dash(intervals, 2, 0);
+
+ SkPaint paint;
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setPathEffect(&dash);
+
+ SkPath filteredPath;
+ SkStrokeRec rec(paint);
+ REPORTER_ASSERT(reporter, !dash.filterPath(&filteredPath, path, &rec));
+ REPORTER_ASSERT(reporter, filteredPath.isEmpty());
+}
+
static void TestDrawPath(skiatest::Reporter* reporter) {
test_giantaa(reporter);
test_bug533(reporter);
@@ -228,6 +270,8 @@ static void TestDrawPath(skiatest::Reporter* reporter) {
test_crbug_140803(reporter);
test_inversepathwithclip(reporter);
// test_crbug131181(reporter);
+ test_infinite_dash(reporter);
+ test_crbug_165432(reporter);
}
#include "TestClassDef.h"
diff --git a/tests/EmptyPathTest.cpp b/tests/EmptyPathTest.cpp
index 4cff190afb..be7d529d20 100644
--- a/tests/EmptyPathTest.cpp
+++ b/tests/EmptyPathTest.cpp
@@ -28,7 +28,7 @@ static void drawAndTest(skiatest::Reporter* reporter, const SkPath& path,
// explicitly specify a trim rowbytes, so we have no padding on each row
bm.setConfig(SkBitmap::kARGB_8888_Config, DIMENSION, DIMENSION, DIMENSION*4);
bm.allocPixels();
- bm.eraseColor(0);
+ bm.eraseColor(SK_ColorTRANSPARENT);
SkCanvas canvas(bm);
SkPaint p(paint);
diff --git a/tests/GLProgramsTest.cpp b/tests/GLProgramsTest.cpp
index deaf0342ba..46d0820079 100644
--- a/tests/GLProgramsTest.cpp
+++ b/tests/GLProgramsTest.cpp
@@ -13,42 +13,33 @@
#if SK_SUPPORT_GPU && SK_ALLOW_STATIC_GLOBAL_INITIALIZERS
#include "gl/GrGpuGL.h"
-#include "GrProgramStageFactory.h"
+#include "GrBackendEffectFactory.h"
#include "effects/GrConfigConversionEffect.h"
-#include "GrRandom.h"
+#include "SkRandom.h"
#include "Test.h"
namespace {
-// GrRandoms nextU() values have patterns in the low bits
+// SkRandoms nextU() values have patterns in the low bits
// So using nextU() % array_count might never take some values.
-int random_int(GrRandom* r, int count) {
+int random_int(SkRandom* r, int count) {
return (int)(r->nextF() * count);
}
-bool random_bool(GrRandom* r) {
+bool random_bool(SkRandom* r) {
return r->nextF() > .5f;
}
-typedef GrGLProgram::StageDesc StageDesc;
-// TODO: Effects should be able to register themselves for inclusion in the
-// randomly generated shaders. They should be able to configure themselves
-// randomly.
-const GrCustomStage* create_random_effect(StageDesc* stageDesc,
- GrRandom* random,
- GrContext* context,
- GrTexture* dummyTextures[]) {
-
- // The new code uses SkRandom not GrRandom.
- // TODO: Remove GrRandom.
+const GrEffect* create_random_effect(SkRandom* random,
+ GrContext* context,
+ GrTexture* dummyTextures[]) {
+
SkRandom sk_random;
sk_random.setSeed(random->nextU());
- GrCustomStage* stage = GrCustomStageTestFactory::CreateStage(&sk_random,
- context,
- dummyTextures);
- GrAssert(stage);
- return stage;
+ GrEffect* effect = GrEffectTestFactory::CreateStage(&sk_random, context, dummyTextures);
+ GrAssert(effect);
+ return effect;
}
}
@@ -64,16 +55,9 @@ bool GrGpuGL::programUnitTest() {
dummyDesc.fHeight = 22;
SkAutoTUnref<GrTexture> dummyTexture2(this->createTexture(dummyDesc, NULL, 0));
- // GrGLSLGeneration glslGeneration =
- GrGetGLSLGeneration(this->glBinding(), this->glInterface());
- static const int STAGE_OPTS[] = {
- 0,
- StageDesc::kNoPerspective_OptFlagBit,
- };
-
static const int NUM_TESTS = 512;
- GrRandom random;
+ SkRandom random;
for (int t = 0; t < NUM_TESTS; ++t) {
#if 0
@@ -108,24 +92,23 @@ bool GrGpuGL::programUnitTest() {
pdesc.fVertexLayout |= GrDrawTarget::kEdge_VertexLayoutBit;
if (this->getCaps().shaderDerivativeSupport()) {
pdesc.fVertexEdgeType = (GrDrawState::VertexEdgeType) random_int(&random, GrDrawState::kVertexEdgeTypeCnt);
+ pdesc.fDiscardIfOutsideEdge = random.nextBool();
} else {
pdesc.fVertexEdgeType = GrDrawState::kHairLine_EdgeType;
+ pdesc.fDiscardIfOutsideEdge = false;
}
} else {
}
- pdesc.fColorMatrixEnabled = random_bool(&random);
-
if (this->getCaps().dualSourceBlendingSupport()) {
pdesc.fDualSrcOutput = random_int(&random, ProgramDesc::kDualSrcOutputCnt);
} else {
pdesc.fDualSrcOutput = ProgramDesc::kNone_DualSrcOutput;
}
- SkAutoTUnref<const GrCustomStage> customStages[GrDrawState::kNumStages];
+ GrEffectStage stages[GrDrawState::kNumStages];
for (int s = 0; s < GrDrawState::kNumStages; ++s) {
- StageDesc& stage = pdesc.fStages[s];
// enable the stage?
if (random_bool(&random)) {
// use separate tex coords?
@@ -133,35 +116,29 @@ bool GrGpuGL::programUnitTest() {
int t = random_int(&random, GrDrawState::kMaxTexCoords);
pdesc.fVertexLayout |= StageTexCoordVertexLayoutBit(s, t);
}
- stage.setEnabled(true);
- }
- // use text-formatted verts?
- if (random_bool(&random)) {
- pdesc.fVertexLayout |= kTextFormat_VertexLayoutBit;
- }
-
- stage.fCustomStageKey = 0;
-
- stage.fOptFlags |= STAGE_OPTS[random_int(&random, GR_ARRAY_COUNT(STAGE_OPTS))];
+ // use text-formatted verts?
+ if (random_bool(&random)) {
+ pdesc.fVertexLayout |= kTextFormat_VertexLayoutBit;
+ }
- if (stage.isEnabled()) {
GrTexture* dummyTextures[] = {dummyTexture1.get(), dummyTexture2.get()};
- customStages[s].reset(create_random_effect(&stage,
- &random,
- getContext(),
- dummyTextures));
- if (NULL != customStages[s]) {
- stage.fCustomStageKey =
- customStages[s]->getFactory().glStageKey(*customStages[s], this->glCaps());
+ SkAutoTUnref<const GrEffect> effect(create_random_effect(&random,
+ getContext(),
+ dummyTextures));
+ stages[s].setEffect(effect.get());
+ if (NULL != stages[s].getEffect()) {
+ pdesc.fEffectKeys[s] =
+ stages[s].getEffect()->getFactory().glEffectKey(stages[s], this->glCaps());
}
}
}
- GR_STATIC_ASSERT(sizeof(customStages) ==
- GrDrawState::kNumStages * sizeof(GrCustomStage*));
- const GrCustomStage** stages = reinterpret_cast<const GrCustomStage**>(&customStages);
+ const GrEffectStage* stagePtrs[GrDrawState::kNumStages];
+ for (int s = 0; s < GrDrawState::kNumStages; ++s) {
+ stagePtrs[s] = &stages[s];
+ }
SkAutoTUnref<GrGLProgram> program(GrGLProgram::Create(this->glContextInfo(),
pdesc,
- stages));
+ stagePtrs));
if (NULL == program.get()) {
return false;
}
@@ -174,24 +151,31 @@ static void GLProgramsTest(skiatest::Reporter* reporter, GrContext* context) {
REPORTER_ASSERT(reporter, shadersGpu->programUnitTest());
}
-
#include "TestClassDef.h"
DEFINE_GPUTESTCLASS("GLPrograms", GLProgramsTestClass, GLProgramsTest)
// This is evil evil evil. The linker may throw away whole translation units as dead code if it
-// thinks none of the functions are called. It will do this even if there are static initilializers
+// thinks none of the functions are called. It will do this even if there are static initializers
// in the unit that could pass pointers to functions from the unit out to other translation units!
// We force some of the effects that would otherwise be discarded to link here.
#include "SkLightingImageFilter.h"
#include "SkMagnifierImageFilter.h"
+#include "SkColorMatrixFilter.h"
void forceLinking();
void forceLinking() {
SkLightingImageFilter::CreateDistantLitDiffuse(SkPoint3(0,0,0), 0, 0, 0);
SkMagnifierImageFilter mag(SkRect::MakeWH(SK_Scalar1, SK_Scalar1), SK_Scalar1);
- GrConfigConversionEffect::Create(NULL, false);
+ GrEffectStage dummyStage;
+ GrConfigConversionEffect::InstallEffect(NULL,
+ false,
+ GrConfigConversionEffect::kNone_PMConversion,
+ SkMatrix::I(),
+ &dummyStage);
+ SkScalar matrix[20];
+ SkColorMatrixFilter cmf(matrix);
}
#endif
diff --git a/tests/GpuBitmapCopyTest.cpp b/tests/GpuBitmapCopyTest.cpp
index 03890480c2..63e3cca4bc 100644
--- a/tests/GpuBitmapCopyTest.cpp
+++ b/tests/GpuBitmapCopyTest.cpp
@@ -10,7 +10,10 @@
#include "GrContext.h"
#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkColor.h"
#include "SkGpuDevice.h"
+#include "SkPaint.h"
#include "SkPixelRef.h"
#include "SkRect.h"
#include "Test.h"
@@ -29,9 +32,95 @@ struct Pair {
const char* fValid;
};
+extern bool getUpperLeftFromOffset(const SkBitmap& bm, int* x, int* y);
+extern size_t getSubOffset(const SkBitmap& bm, int x, int y);
+
+/**
+ * Tests that getUpperLeftFromOffset and getSubOffset agree with each other.
+ */
+static void TestSubsetHelpers(skiatest::Reporter* reporter, const SkBitmap& bitmap){
+ int x, y;
+ bool upperLeft = getUpperLeftFromOffset(bitmap, &x, &y);
+ REPORTER_ASSERT(reporter, upperLeft);
+ REPORTER_ASSERT(reporter, getSubOffset(bitmap, x, y) == bitmap.pixelRefOffset());
+}
+
+/**
+ * Check to ensure that copying a GPU-backed SkBitmap behaved as expected.
+ * @param reporter Used to report failures.
+ * @param desiredConfig Config being copied to. If the copy succeeded, dst must have this Config.
+ * @param success True if the copy succeeded.
+ * @param src A GPU-backed SkBitmap that had copyTo or deepCopyTo called on it.
+ * @param dst SkBitmap that was copied to.
+ * @param deepCopy True if deepCopyTo was used; false if copyTo was used.
+ * @param subset Portion of src's SkPixelRef that src represents. dst should represent the same
+ * portion after the copy. Pass NULL for all pixels.
+ */
+static void TestIndividualCopy(skiatest::Reporter* reporter, const SkBitmap::Config desiredConfig,
+ const bool success, const SkBitmap& src, const SkBitmap& dst,
+ const bool deepCopy = true, const SkIRect* subset = NULL) {
+ if (success) {
+ REPORTER_ASSERT(reporter, src.width() == dst.width());
+ REPORTER_ASSERT(reporter, src.height() == dst.height());
+ REPORTER_ASSERT(reporter, dst.config() == desiredConfig);
+ if (src.config() == dst.config()) {
+ // FIXME: When calling copyTo (so deepCopy is false here), sometimes we copy the pixels
+ // exactly, in which case the IDs should be the same, but sometimes we do a bitmap draw,
+ // in which case the IDs should not be the same. Is there any way to determine which is
+ // the case at this point?
+ if (deepCopy) {
+ REPORTER_ASSERT(reporter, src.getGenerationID() == dst.getGenerationID());
+ }
+ REPORTER_ASSERT(reporter, src.pixelRef() != NULL && dst.pixelRef() != NULL);
+
+ // Do read backs and make sure that the two are the same.
+ SkBitmap srcReadBack, dstReadBack;
+ {
+ SkASSERT(src.getTexture() != NULL);
+ bool readBack = src.pixelRef()->readPixels(&srcReadBack, subset);
+ REPORTER_ASSERT(reporter, readBack);
+ }
+ if (dst.getTexture() != NULL) {
+ bool readBack = dst.pixelRef()->readPixels(&dstReadBack, subset);
+ REPORTER_ASSERT(reporter, readBack);
+ } else {
+ // If dst is not a texture, do a copy instead, to the same config as srcReadBack.
+ bool copy = dst.copyTo(&dstReadBack, srcReadBack.config());
+ REPORTER_ASSERT(reporter, copy);
+ }
+
+ SkAutoLockPixels srcLock(srcReadBack);
+ SkAutoLockPixels dstLock(dstReadBack);
+ REPORTER_ASSERT(reporter, srcReadBack.readyToDraw() && dstReadBack.readyToDraw());
+
+ const char* srcP = static_cast<const char*>(srcReadBack.getAddr(0, 0));
+ const char* dstP = static_cast<const char*>(dstReadBack.getAddr(0, 0));
+ REPORTER_ASSERT(reporter, srcP != dstP);
+
+ REPORTER_ASSERT(reporter, !memcmp(srcP, dstP, srcReadBack.getSize()));
+ } else {
+ REPORTER_ASSERT(reporter, src.getGenerationID() != dst.getGenerationID());
+ }
+
+ // If the copy used a subset, test the pixel offset calculation functions.
+ if (subset != NULL) {
+ TestSubsetHelpers(reporter, dst);
+ }
+ } else {
+ // dst should be unchanged from its initial state
+ REPORTER_ASSERT(reporter, dst.config() == SkBitmap::kNo_Config);
+ REPORTER_ASSERT(reporter, dst.width() == 0);
+ REPORTER_ASSERT(reporter, dst.height() == 0);
+ }
+
+}
+
// Stripped down version of TestBitmapCopy that checks basic fields (width, height, config, genID)
// to ensure that they were copied properly.
static void TestGpuBitmapCopy(skiatest::Reporter* reporter, GrContext* grContext) {
+#ifdef SK_BUILD_FOR_ANDROID // https://code.google.com/p/skia/issues/detail?id=753
+ return;
+#endif
if (NULL == grContext) {
return;
}
@@ -45,14 +134,30 @@ static void TestGpuBitmapCopy(skiatest::Reporter* reporter, GrContext* grContext
const int H = 33;
for (size_t i = 0; i < SK_ARRAY_COUNT(gPairs); i++) {
- for (size_t j = 0; j < SK_ARRAY_COUNT(gPairs); j++) {
- SkBitmap src, dst;
+ SkBitmap src, dst;
+
+ SkGpuDevice* device = SkNEW_ARGS(SkGpuDevice, (grContext, gPairs[i].fConfig, W, H));
+ SkAutoUnref aur(device);
+ src = device->accessBitmap(false);
+ device->clear(SK_ColorWHITE);
- SkGpuDevice* device = SkNEW_ARGS(SkGpuDevice, (grContext, gPairs[i].fConfig, W, H));
- SkAutoUnref aur(device);
- src = device->accessBitmap(false);
- device->clear(SK_ColorWHITE);
+ // Draw something different to the same portion of the bitmap that we will extract as a
+ // subset, so that comparing the pixels of the subset will be meaningful.
+ SkIRect subsetRect = SkIRect::MakeLTRB(W/2, H/2, W, H);
+ SkCanvas drawingCanvas(device);
+ SkPaint paint;
+ paint.setColor(SK_ColorRED);
+ drawingCanvas.drawRect(SkRect::MakeFromIRect(subsetRect), paint);
+ // Extract a subset. If this succeeds we will test copying the subset.
+ SkBitmap subset;
+ const bool extracted = src.extractSubset(&subset, subsetRect);
+ if (extracted) {
+ TestSubsetHelpers(reporter, subset);
+ }
+
+ for (size_t j = 0; j < SK_ARRAY_COUNT(gPairs); j++) {
+ dst.reset();
bool success = src.deepCopyTo(&dst, gPairs[j].fConfig);
bool expected = gPairs[i].fValid[j] != '0';
if (success != expected) {
@@ -73,37 +178,27 @@ static void TestGpuBitmapCopy(skiatest::Reporter* reporter, GrContext* grContext
reporter->reportFailed(str);
}
- if (success) {
- REPORTER_ASSERT(reporter, src.width() == dst.width());
- REPORTER_ASSERT(reporter, src.height() == dst.height());
- REPORTER_ASSERT(reporter, dst.config() == gPairs[j].fConfig);
- if (src.config() == dst.config()) {
- REPORTER_ASSERT(reporter, src.getGenerationID() == dst.getGenerationID());
- // Do read backs and make sure that the two are the same.
- SkBitmap srcReadBack, dstReadBack;
- REPORTER_ASSERT(reporter, src.pixelRef() != NULL
- && dst.pixelRef() != NULL);
- src.pixelRef()->readPixels(&srcReadBack);
- dst.pixelRef()->readPixels(&dstReadBack);
- SkAutoLockPixels srcLock(srcReadBack);
- SkAutoLockPixels dstLock(dstReadBack);
- REPORTER_ASSERT(reporter, srcReadBack.readyToDraw()
- && dstReadBack.readyToDraw());
- const char* srcP = (const char*)srcReadBack.getAddr(0, 0);
- const char* dstP = (const char*)dstReadBack.getAddr(0, 0);
- REPORTER_ASSERT(reporter, srcP != dstP);
- REPORTER_ASSERT(reporter, !memcmp(srcP, dstP, srcReadBack.getSize()));
- } else {
- REPORTER_ASSERT(reporter, src.getGenerationID() != dst.getGenerationID());
- }
- } else {
- // dst should be unchanged from its initial state
- REPORTER_ASSERT(reporter, dst.config() == SkBitmap::kNo_Config);
- REPORTER_ASSERT(reporter, dst.width() == 0);
- REPORTER_ASSERT(reporter, dst.height() == 0);
+ TestIndividualCopy(reporter, gPairs[j].fConfig, success, src, dst);
+
+ // Test copying the subset bitmap, using both copyTo and deepCopyTo.
+ if (extracted) {
+ SkBitmap subsetCopy;
+ success = subset.copyTo(&subsetCopy, gPairs[j].fConfig);
+ REPORTER_ASSERT(reporter, success == expected);
+ REPORTER_ASSERT(reporter, success == canSucceed);
+ TestIndividualCopy(reporter, gPairs[j].fConfig, success, subset, subsetCopy, false,
+ &subsetRect);
+
+ // Reset the bitmap so that a failed copyTo will leave it in the expected state.
+ subsetCopy.reset();
+ success = subset.deepCopyTo(&subsetCopy, gPairs[j].fConfig);
+ REPORTER_ASSERT(reporter, success == expected);
+ REPORTER_ASSERT(reporter, success == canSucceed);
+ TestIndividualCopy(reporter, gPairs[j].fConfig, success, subset, subsetCopy, true,
+ &subsetRect);
}
} // for (size_t j = ...
- }
+ } // for (size_t i = ...
}
#include "TestClassDef.h"
diff --git a/tests/InfRectTest.cpp b/tests/InfRectTest.cpp
index 1dc6ca7d0c..808bcee811 100644
--- a/tests/InfRectTest.cpp
+++ b/tests/InfRectTest.cpp
@@ -6,6 +6,7 @@
* found in the LICENSE file.
*/
#include "Test.h"
+#include "SkRandom.h"
#include "SkRect.h"
#ifdef SK_SCALAR_IS_FLOAT
@@ -14,6 +15,37 @@ static float make_zero() {
}
#endif
+static void test_center(skiatest::Reporter* reporter) {
+ static const struct {
+ SkIRect fRect;
+ SkIPoint fCenter;
+ } data[] = {
+ { { 0, 0, 0, 0 }, { 0, 0 } },
+ { { 0, 0, 1, 1 }, { 0, 0 } },
+ { { -1, -1, 0, 0 }, { -1, -1 } },
+ { { 0, 0, 10, 7 }, { 5, 3 } },
+ { { 0, 0, 11, 6 }, { 5, 3 } },
+ };
+ for (size_t index = 0; index < SK_ARRAY_COUNT(data); ++index) {
+ REPORTER_ASSERT(reporter,
+ data[index].fRect.centerX() == data[index].fCenter.x());
+ REPORTER_ASSERT(reporter,
+ data[index].fRect.centerY() == data[index].fCenter.y());
+ }
+
+ SkRandom rand;
+ for (int i = 0; i < 10000; ++i) {
+ SkIRect r;
+
+ r.set(rand.nextS() >> 2, rand.nextS() >> 2,
+ rand.nextS() >> 2, rand.nextS() >> 2);
+ int cx = r.centerX();
+ int cy = r.centerY();
+ REPORTER_ASSERT(reporter, ((r.left() + r.right()) >> 1) == cx);
+ REPORTER_ASSERT(reporter, ((r.top() + r.bottom()) >> 1) == cy);
+ }
+}
+
static void check_invalid(skiatest::Reporter* reporter,
SkScalar l, SkScalar t, SkScalar r, SkScalar b) {
SkRect rect;
@@ -47,6 +79,8 @@ static void TestInfRect(skiatest::Reporter* reporter) {
check_invalid(reporter, small, invalid[i], big, big);
check_invalid(reporter, invalid[i], small, big, big);
}
+
+ test_center(reporter);
}
// need tests for SkStrSearch
diff --git a/tests/LListTest.cpp b/tests/LListTest.cpp
new file mode 100644
index 0000000000..89c4971b96
--- /dev/null
+++ b/tests/LListTest.cpp
@@ -0,0 +1,321 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "Test.h"
+#include "SkRandom.h"
+#include "SkTInternalLList.h"
+#include "SkTLList.h"
+
+class ListElement {
+public:
+ ListElement(int id) : fID(id) {
+ }
+ bool operator== (const ListElement& other) { return fID == other.fID; }
+
+#ifdef SK_ENABLE_INST_COUNT
+ // Make the instance count available publicly.
+ static int InstanceCount() { return GetInstanceCount(); }
+#endif
+
+ int fID;
+
+private:
+ SK_DECLARE_INST_COUNT_ROOT(ListElement);
+ SK_DECLARE_INTERNAL_LLIST_INTERFACE(ListElement);
+};
+
+SK_DEFINE_INST_COUNT(ListElement);
+
+static void check_list(const SkTInternalLList<ListElement>& list,
+ skiatest::Reporter* reporter,
+ bool empty,
+ int numElements,
+ bool in0, bool in1, bool in2, bool in3,
+ ListElement elements[4]) {
+
+ REPORTER_ASSERT(reporter, empty == list.isEmpty());
+#if SK_DEBUG
+ list.validate();
+ REPORTER_ASSERT(reporter, numElements == list.countEntries());
+ REPORTER_ASSERT(reporter, in0 == list.isInList(&elements[0]));
+ REPORTER_ASSERT(reporter, in1 == list.isInList(&elements[1]));
+ REPORTER_ASSERT(reporter, in2 == list.isInList(&elements[2]));
+ REPORTER_ASSERT(reporter, in3 == list.isInList(&elements[3]));
+#endif
+}
+
+static void TestTInternalLList(skiatest::Reporter* reporter) {
+ SkTInternalLList<ListElement> list;
+ ListElement elements[4] = {
+ ListElement(0),
+ ListElement(1),
+ ListElement(2),
+ ListElement(3),
+ };
+
+ // list should be empty to start with
+ check_list(list, reporter, true, 0, false, false, false, false, elements);
+
+ list.addToHead(&elements[0]);
+
+ check_list(list, reporter, false, 1, true, false, false, false, elements);
+
+ list.addToHead(&elements[1]);
+ list.addToHead(&elements[2]);
+ list.addToHead(&elements[3]);
+
+ check_list(list, reporter, false, 4, true, true, true, true, elements);
+
+ // test out iterators
+ typedef SkTInternalLList<ListElement>::Iter Iter;
+ Iter iter;
+
+ ListElement* cur = iter.init(list, Iter::kHead_IterStart);
+ for (int i = 0; NULL != cur; ++i, cur = iter.next()) {
+ REPORTER_ASSERT(reporter, cur->fID == 3-i);
+ }
+
+ cur = iter.init(list, Iter::kTail_IterStart);
+ for (int i = 0; NULL != cur; ++i, cur = iter.prev()) {
+ REPORTER_ASSERT(reporter, cur->fID == i);
+ }
+
+ // remove middle, frontmost then backmost
+ list.remove(&elements[1]);
+ list.remove(&elements[3]);
+ list.remove(&elements[0]);
+
+ check_list(list, reporter, false, 1, false, false, true, false, elements);
+
+ // remove last element
+ list.remove(&elements[2]);
+
+ // list should be empty again
+ check_list(list, reporter, true, 0, false, false, false, false, elements);
+
+ // test out methods that add to the middle of the list.
+ list.addAfter(&elements[1], NULL);
+ check_list(list, reporter, false, 1, false, true, false, false, elements);
+
+ list.remove(&elements[1]);
+
+ list.addBefore(&elements[1], NULL);
+ check_list(list, reporter, false, 1, false, true, false, false, elements);
+
+ list.addBefore(&elements[0], &elements[1]);
+ check_list(list, reporter, false, 2, true, true, false, false, elements);
+
+ list.addAfter(&elements[3], &elements[1]);
+ check_list(list, reporter, false, 3, true, true, false, true, elements);
+
+ list.addBefore(&elements[2], &elements[3]);
+ check_list(list, reporter, false, 4, true, true, true, true, elements);
+
+ cur = iter.init(list, Iter::kHead_IterStart);
+ for (int i = 0; NULL != cur; ++i, cur = iter.next()) {
+ REPORTER_ASSERT(reporter, cur->fID == i);
+ }
+}
+
+static void TestTLList(skiatest::Reporter* reporter) {
+ typedef SkTLList<ListElement> ElList;
+ typedef ElList::Iter Iter;
+ SkRandom random;
+
+ for (int i = 1; i <= 16; i *= 2) {
+
+ ElList list1(i);
+ ElList list2(i);
+ Iter iter1;
+ Iter iter2;
+ Iter iter3;
+ Iter iter4;
+
+#ifdef SK_ENABLE_INST_COUNT
+ SkASSERT(0 == ListElement::InstanceCount());
+#endif
+
+ REPORTER_ASSERT(reporter, list1.isEmpty());
+ REPORTER_ASSERT(reporter, NULL == iter1.init(list1, Iter::kHead_IterStart));
+ REPORTER_ASSERT(reporter, NULL == iter1.init(list1, Iter::kTail_IterStart));
+ // Try popping an empty list
+ list1.popHead();
+ list1.popTail();
+ REPORTER_ASSERT(reporter, list1.isEmpty());
+ REPORTER_ASSERT(reporter, list1 == list2);
+
+ // Create two identical lists, one by appending to head and the other to the tail.
+ list1.addToHead(ListElement(1));
+ list2.addToTail(ListElement(1));
+#ifdef SK_ENABLE_INST_COUNT
+ SkASSERT(2 == ListElement::InstanceCount());
+#endif
+ iter1.init(list1, Iter::kHead_IterStart);
+ iter2.init(list1, Iter::kTail_IterStart);
+ REPORTER_ASSERT(reporter, iter1.get()->fID == iter2.get()->fID);
+ iter3.init(list2, Iter::kHead_IterStart);
+ iter4.init(list2, Iter::kTail_IterStart);
+ REPORTER_ASSERT(reporter, iter3.get()->fID == iter1.get()->fID);
+ REPORTER_ASSERT(reporter, iter4.get()->fID == iter1.get()->fID);
+ REPORTER_ASSERT(reporter, list1 == list2);
+
+ list2.reset();
+
+ // use both before/after in-place construction on an empty list
+ SkNEW_INSERT_IN_LLIST_BEFORE(&list2, list2.headIter(), ListElement, (1));
+ REPORTER_ASSERT(reporter, list2 == list1);
+ list2.reset();
+
+ SkNEW_INSERT_IN_LLIST_AFTER(&list2, list2.tailIter(), ListElement, (1));
+ REPORTER_ASSERT(reporter, list2 == list1);
+
+ // add an element to the second list, check that iters are still valid
+ iter3.init(list2, Iter::kHead_IterStart);
+ iter4.init(list2, Iter::kTail_IterStart);
+ list2.addToHead(ListElement(2));
+
+#ifdef SK_ENABLE_INST_COUNT
+ SkASSERT(3 == ListElement::InstanceCount());
+#endif
+
+ REPORTER_ASSERT(reporter, iter3.get()->fID == iter1.get()->fID);
+ REPORTER_ASSERT(reporter, iter4.get()->fID == iter1.get()->fID);
+ REPORTER_ASSERT(reporter, 1 == Iter(list2, Iter::kTail_IterStart).get()->fID);
+ REPORTER_ASSERT(reporter, 2 == Iter(list2, Iter::kHead_IterStart).get()->fID);
+ REPORTER_ASSERT(reporter, list1 != list2);
+ list1.addToHead(ListElement(2));
+ REPORTER_ASSERT(reporter, list1 == list2);
+#ifdef SK_ENABLE_INST_COUNT
+ SkASSERT(4 == ListElement::InstanceCount());
+#endif
+ REPORTER_ASSERT(reporter, !list1.isEmpty());
+
+ list1.reset();
+ list2.reset();
+#ifdef SK_ENABLE_INST_COUNT
+ SkASSERT(0 == ListElement::InstanceCount());
+#endif
+ REPORTER_ASSERT(reporter, list1.isEmpty() && list2.isEmpty());
+
+ // randomly perform insertions and deletions on a list and perform tests
+ int count = 0;
+ for (int j = 0; j < 100; ++j) {
+ if (list1.isEmpty() || random.nextBiasedBool(3 * SK_Scalar1 / 4)) {
+ int id = j;
+ // Choose one of three ways to insert a new element: at the head, at the tail,
+ // before a random element, after a random element
+ int numValidMethods = 0 == count ? 2 : 4;
+ int insertionMethod = random.nextULessThan(numValidMethods);
+ switch (insertionMethod) {
+ case 0:
+ list1.addToHead(ListElement(id));
+ break;
+ case 1:
+ list1.addToTail(ListElement(id));
+ break;
+ case 2: // fallthru to share code that picks random element.
+ case 3: {
+ int n = random.nextULessThan(list1.count());
+ Iter iter = list1.headIter();
+ // remember the elements before/after the insertion point.
+ while (n--) {
+ iter.next();
+ }
+ Iter prev(iter);
+ Iter next(iter);
+ next.next();
+ prev.prev();
+
+ SkASSERT(NULL != iter.get());
+ // insert either before or after the iterator, then check that the
+ // surrounding sequence is correct.
+ if (2 == insertionMethod) {
+ SkNEW_INSERT_IN_LLIST_BEFORE(&list1, iter, ListElement, (id));
+ Iter newItem(iter);
+ newItem.prev();
+ REPORTER_ASSERT(reporter, newItem.get()->fID == id);
+
+ if (NULL != next.get()) {
+ REPORTER_ASSERT(reporter, next.prev()->fID == iter.get()->fID);
+ }
+ if (NULL != prev.get()) {
+ REPORTER_ASSERT(reporter, prev.next()->fID == id);
+ }
+ } else {
+ SkNEW_INSERT_IN_LLIST_AFTER(&list1, iter, ListElement, (id));
+ Iter newItem(iter);
+ newItem.next();
+ REPORTER_ASSERT(reporter, newItem.get()->fID == id);
+
+ if (NULL != next.get()) {
+ REPORTER_ASSERT(reporter, next.prev()->fID == id);
+ }
+ if (NULL != prev.get()) {
+ REPORTER_ASSERT(reporter, prev.next()->fID == iter.get()->fID);
+ }
+ }
+ }
+ }
+ ++count;
+ } else {
+ // walk to a random place either forward or backwards and remove.
+ int n = random.nextULessThan(list1.count());
+ Iter::IterStart start;
+ ListElement* (Iter::*incrFunc)();
+
+ if (random.nextBool()) {
+ start = Iter::kHead_IterStart;
+ incrFunc = &Iter::next;
+ } else {
+ start = Iter::kTail_IterStart;
+ incrFunc = &Iter::prev;
+ }
+
+ // find the element
+ Iter iter(list1, start);
+ while (n--) {
+ REPORTER_ASSERT(reporter, NULL != iter.get());
+ (iter.*incrFunc)();
+ }
+ REPORTER_ASSERT(reporter, NULL != iter.get());
+
+ // remember the prev and next elements from the element to be removed
+ Iter prev = iter;
+ Iter next = iter;
+ prev.prev();
+ next.next();
+ list1.remove(iter.get());
+
+ // make sure the remembered next/prev iters still work
+ Iter pn = prev; pn.next();
+ Iter np = next; np.prev();
+ // pn should match next unless the target node was the head, in which case prev
+ // walked off the list.
+ REPORTER_ASSERT(reporter, pn.get() == next.get() || NULL == prev.get());
+ // Similarly, np should match prev unless next originally walked off the tail.
+ REPORTER_ASSERT(reporter, np.get() == prev.get() || NULL == next.get());
+ --count;
+ }
+ REPORTER_ASSERT(reporter, count == list1.count());
+#ifdef SK_ENABLE_INST_COUNT
+ SkASSERT(count == ListElement::InstanceCount());
+#endif
+ }
+ list1.reset();
+#ifdef SK_ENABLE_INST_COUNT
+ SkASSERT(0 == ListElement::InstanceCount());
+#endif
+ }
+}
+
+static void test_llists(skiatest::Reporter* reporter) {
+ TestTInternalLList(reporter);
+ TestTLList(reporter);
+}
+
+#include "TestClassDef.h"
+DEFINE_TESTCLASS("LList", TestLListClass, test_llists)
diff --git a/tests/Matrix44Test.cpp b/tests/Matrix44Test.cpp
index 703787701d..a680b44bda 100644
--- a/tests/Matrix44Test.cpp
+++ b/tests/Matrix44Test.cpp
@@ -1,13 +1,21 @@
-
/*
* Copyright 2011 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
+
#include "Test.h"
#include "SkMatrix44.h"
+static bool nearly_equal_double(double a, double b) {
+ const double tolerance = 1e-7;
+ double diff = a - b;
+ if (diff < 0)
+ diff = -diff;
+ return diff <= tolerance;
+}
+
static bool nearly_equal_scalar(SkMScalar a, SkMScalar b) {
// Note that we get more compounded error for multiple operations when
// SK_SCALAR_IS_FIXED.
@@ -64,6 +72,165 @@ static bool is_identity(const SkMatrix44& m) {
return nearly_equal(m, identity);
}
+///////////////////////////////////////////////////////////////////////////////
+static bool bits_isonly(int value, int mask) {
+ return 0 == (value & ~mask);
+}
+
+static void test_constructor(skiatest::Reporter* reporter) {
+ // Allocate a matrix on the heap
+ SkMatrix44* placeholderMatrix = new SkMatrix44();
+ for (int row = 0; row < 4; ++row) {
+ for (int col = 0; col < 4; ++col) {
+ placeholderMatrix->setDouble(row, col, row * col);
+ }
+ }
+
+ // Use placement-new syntax to trigger the constructor on top of the heap
+ // address we already initialized. This allows us to check that the
+ // constructor did avoid initializing the matrix contents.
+ SkMatrix44* testMatrix = new(placeholderMatrix) SkMatrix44(SkMatrix44::kUninitialized_Constructor);
+ REPORTER_ASSERT(reporter, testMatrix == placeholderMatrix);
+ REPORTER_ASSERT(reporter, !testMatrix->isIdentity());
+ for (int row = 0; row < 4; ++row) {
+ for (int col = 0; col < 4; ++col) {
+ REPORTER_ASSERT(reporter, nearly_equal_double(row * col, testMatrix->getDouble(row, col)));
+ }
+ }
+
+ // Verify that kIdentity_Constructor really does initialize to an identity matrix.
+ testMatrix = 0;
+ testMatrix = new(placeholderMatrix) SkMatrix44(SkMatrix44::kIdentity_Constructor);
+ REPORTER_ASSERT(reporter, testMatrix == placeholderMatrix);
+ REPORTER_ASSERT(reporter, testMatrix->isIdentity());
+ REPORTER_ASSERT(reporter, *testMatrix == SkMatrix44::I());
+}
+
+static void test_translate(skiatest::Reporter* reporter) {
+ SkMatrix44 mat, inverse;
+
+ mat.setTranslate(0, 0, 0);
+ REPORTER_ASSERT(reporter, bits_isonly(mat.getType(), SkMatrix44::kIdentity_Mask));
+ mat.setTranslate(1, 2, 3);
+ REPORTER_ASSERT(reporter, bits_isonly(mat.getType(), SkMatrix44::kTranslate_Mask));
+ REPORTER_ASSERT(reporter, mat.invert(&inverse));
+ REPORTER_ASSERT(reporter, bits_isonly(inverse.getType(), SkMatrix44::kTranslate_Mask));
+
+ SkMatrix44 a, b, c;
+ a.set3x3(1, 2, 3, 4, 5, 6, 7, 8, 9);
+ b.setTranslate(10, 11, 12);
+
+ c.setConcat(a, b);
+ mat = a;
+ mat.preTranslate(10, 11, 12);
+ REPORTER_ASSERT(reporter, mat == c);
+
+ c.setConcat(b, a);
+ mat = a;
+ mat.postTranslate(10, 11, 12);
+ REPORTER_ASSERT(reporter, mat == c);
+}
+
+static void test_scale(skiatest::Reporter* reporter) {
+ SkMatrix44 mat, inverse;
+
+ mat.setScale(1, 1, 1);
+ REPORTER_ASSERT(reporter, bits_isonly(mat.getType(), SkMatrix44::kIdentity_Mask));
+ mat.setScale(1, 2, 3);
+ REPORTER_ASSERT(reporter, bits_isonly(mat.getType(), SkMatrix44::kScale_Mask));
+ REPORTER_ASSERT(reporter, mat.invert(&inverse));
+ REPORTER_ASSERT(reporter, bits_isonly(inverse.getType(), SkMatrix44::kScale_Mask));
+
+ SkMatrix44 a, b, c;
+ a.set3x3(1, 2, 3, 4, 5, 6, 7, 8, 9);
+ b.setScale(10, 11, 12);
+
+ c.setConcat(a, b);
+ mat = a;
+ mat.preScale(10, 11, 12);
+ REPORTER_ASSERT(reporter, mat == c);
+
+ c.setConcat(b, a);
+ mat = a;
+ mat.postScale(10, 11, 12);
+ REPORTER_ASSERT(reporter, mat == c);
+}
+
+static void make_i(SkMatrix44* mat) { mat->setIdentity(); }
+static void make_t(SkMatrix44* mat) { mat->setTranslate(1, 2, 3); }
+static void make_s(SkMatrix44* mat) { mat->setScale(1, 2, 3); }
+static void make_st(SkMatrix44* mat) {
+ mat->setScale(1, 2, 3);
+ mat->postTranslate(1, 2, 3);
+}
+static void make_a(SkMatrix44* mat) {
+ mat->setRotateDegreesAbout(1, 2, 3, 45);
+}
+static void make_p(SkMatrix44* mat) {
+ SkMScalar data[] = {
+ 1, 2, 3, 4, 5, 6, 7, 8,
+ 1, 2, 3, 4, 5, 6, 7, 8,
+ };
+ mat->setRowMajor(data);
+}
+
+typedef void (*Make44Proc)(SkMatrix44*);
+
+static const Make44Proc gMakeProcs[] = {
+ make_i, make_t, make_s, make_st, make_a, make_p
+};
+
+static void test_map2(skiatest::Reporter* reporter, const SkMatrix44& mat) {
+ SkMScalar src2[] = { 1, 2 };
+ SkMScalar src4[] = { src2[0], src2[1], 0, 1 };
+ SkMScalar dstA[4], dstB[4];
+
+ for (int i = 0; i < 4; ++i) {
+ dstA[i] = 123456789;
+ dstB[i] = 987654321;
+ }
+
+ mat.map2(src2, 1, dstA);
+ mat.mapMScalars(src4, dstB);
+
+ for (int i = 0; i < 4; ++i) {
+ REPORTER_ASSERT(reporter, dstA[i] == dstB[i]);
+ }
+}
+
+static void test_map2(skiatest::Reporter* reporter) {
+ SkMatrix44 mat;
+
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gMakeProcs); ++i) {
+ gMakeProcs[i](&mat);
+ test_map2(reporter, mat);
+ }
+}
+
+static void test_gettype(skiatest::Reporter* reporter) {
+ SkMatrix44 matrix;
+
+ REPORTER_ASSERT(reporter, matrix.isIdentity());
+ REPORTER_ASSERT(reporter, SkMatrix44::kIdentity_Mask == matrix.getType());
+
+ int expectedMask;
+
+ matrix.set(1, 1, 0);
+ expectedMask = SkMatrix44::kScale_Mask;
+ REPORTER_ASSERT(reporter, matrix.getType() == expectedMask);
+
+ matrix.set(0, 3, 1); // translate-x
+ expectedMask |= SkMatrix44::kTranslate_Mask;
+ REPORTER_ASSERT(reporter, matrix.getType() == expectedMask);
+
+ matrix.set(2, 0, 1);
+ expectedMask |= SkMatrix44::kAffine_Mask;
+ REPORTER_ASSERT(reporter, matrix.getType() == expectedMask);
+
+ matrix.set(3, 2, 1);
+ REPORTER_ASSERT(reporter, matrix.getType() & SkMatrix44::kPerspective_Mask);
+}
+
static void test_common_angles(skiatest::Reporter* reporter) {
SkMatrix44 rot;
// Test precision of rotation in common cases
@@ -76,32 +243,145 @@ static void test_common_angles(skiatest::Reporter* reporter) {
}
}
+static void test_concat(skiatest::Reporter* reporter) {
+ int i;
+ SkMatrix44 a, b, c, d;
+
+ a.setTranslate(10, 10, 10);
+ b.setScale(2, 2, 2);
+
+ SkScalar src[8] = {
+ 0, 0, 0, 1,
+ 1, 1, 1, 1
+ };
+ SkScalar dst[8];
+
+ c.setConcat(a, b);
+
+ d = a;
+ d.preConcat(b);
+ REPORTER_ASSERT(reporter, d == c);
+
+ c.mapScalars(src, dst); c.mapScalars(src + 4, dst + 4);
+ for (i = 0; i < 3; ++i) {
+ REPORTER_ASSERT(reporter, 10 == dst[i]);
+ REPORTER_ASSERT(reporter, 12 == dst[i + 4]);
+ }
+
+ c.setConcat(b, a);
+
+ d = a;
+ d.postConcat(b);
+ REPORTER_ASSERT(reporter, d == c);
+
+ c.mapScalars(src, dst); c.mapScalars(src + 4, dst + 4);
+ for (i = 0; i < 3; ++i) {
+ REPORTER_ASSERT(reporter, 20 == dst[i]);
+ REPORTER_ASSERT(reporter, 22 == dst[i + 4]);
+ }
+}
+
+static void test_determinant(skiatest::Reporter* reporter) {
+ SkMatrix44 a;
+ REPORTER_ASSERT(reporter, nearly_equal_double(1, a.determinant()));
+ a.set(1, 1, 2);
+ REPORTER_ASSERT(reporter, nearly_equal_double(2, a.determinant()));
+ SkMatrix44 b;
+ REPORTER_ASSERT(reporter, a.invert(&b));
+ REPORTER_ASSERT(reporter, nearly_equal_double(0.5, b.determinant()));
+ SkMatrix44 c = b = a;
+ c.set(0, 1, 4);
+ b.set(1, 0, 4);
+ REPORTER_ASSERT(reporter,
+ nearly_equal_double(a.determinant(),
+ b.determinant()));
+ SkMatrix44 d = a;
+ d.set(0, 0, 8);
+ REPORTER_ASSERT(reporter, nearly_equal_double(16, d.determinant()));
+
+ SkMatrix44 e = a;
+ e.postConcat(d);
+ REPORTER_ASSERT(reporter, nearly_equal_double(32, e.determinant()));
+ e.set(0, 0, 0);
+ REPORTER_ASSERT(reporter, nearly_equal_double(0, e.determinant()));
+}
+
+static void test_transpose(skiatest::Reporter* reporter) {
+ SkMatrix44 a;
+ SkMatrix44 b;
+
+ int i = 0;
+ for (int row = 0; row < 4; ++row) {
+ for (int col = 0; col < 4; ++col) {
+ a.setDouble(row, col, i);
+ b.setDouble(col, row, i++);
+ }
+ }
+
+ a.transpose();
+ REPORTER_ASSERT(reporter, nearly_equal(a, b));
+}
+
+static void test_get_set_double(skiatest::Reporter* reporter) {
+ SkMatrix44 a;
+ for (int row = 0; row < 4; ++row) {
+ for (int col = 0; col < 4; ++col) {
+ a.setDouble(row, col, 3.141592653589793);
+ REPORTER_ASSERT(reporter,
+ nearly_equal_double(3.141592653589793,
+ a.getDouble(row, col)));
+ a.setDouble(row, col, 0);
+ REPORTER_ASSERT(reporter,
+ nearly_equal_double(0, a.getDouble(row, col)));
+ }
+ }
+}
+
+static void test_set_row_col_major(skiatest::Reporter* reporter) {
+ SkMatrix44 a, b, c, d;
+ for (int row = 0; row < 4; ++row) {
+ for (int col = 0; col < 4; ++col) {
+ a.setDouble(row, col, row * 4 + col);
+ }
+ }
+
+ double bufferd[16];
+ float bufferf[16];
+ a.asColMajord(bufferd);
+ b.setColMajord(bufferd);
+ REPORTER_ASSERT(reporter, nearly_equal(a, b));
+ b.setRowMajord(bufferd);
+ b.transpose();
+ REPORTER_ASSERT(reporter, nearly_equal(a, b));
+ a.asColMajorf(bufferf);
+ b.setColMajorf(bufferf);
+ REPORTER_ASSERT(reporter, nearly_equal(a, b));
+ b.setRowMajorf(bufferf);
+ b.transpose();
+ REPORTER_ASSERT(reporter, nearly_equal(a, b));
+}
+
static void TestMatrix44(skiatest::Reporter* reporter) {
-#ifdef SK_SCALAR_IS_FLOAT
SkMatrix44 mat, inverse, iden1, iden2, rot;
mat.reset();
- mat.setTranslate(SK_Scalar1, SK_Scalar1, SK_Scalar1);
+ mat.setTranslate(1, 1, 1);
mat.invert(&inverse);
iden1.setConcat(mat, inverse);
REPORTER_ASSERT(reporter, is_identity(iden1));
- mat.setScale(SkIntToScalar(2), SkIntToScalar(2), SkIntToScalar(2));
+ mat.setScale(2, 2, 2);
mat.invert(&inverse);
iden1.setConcat(mat, inverse);
REPORTER_ASSERT(reporter, is_identity(iden1));
- mat.setScale(SK_Scalar1/2, SK_Scalar1/2, SK_Scalar1/2);
+ mat.setScale(SK_MScalar1/2, SK_MScalar1/2, SK_MScalar1/2);
mat.invert(&inverse);
iden1.setConcat(mat, inverse);
REPORTER_ASSERT(reporter, is_identity(iden1));
- mat.setScale(SkIntToScalar(3), SkIntToScalar(5), SkIntToScalar(20));
- rot.setRotateDegreesAbout(
- SkIntToScalar(0),
- SkIntToScalar(0),
- SkIntToScalar(-1),
- SkIntToScalar(90));
+ mat.setScale(3, 3, 3);
+ rot.setRotateDegreesAbout(0, 0, -1, 90);
mat.postConcat(rot);
REPORTER_ASSERT(reporter, mat.invert(NULL));
mat.invert(&inverse);
@@ -139,10 +419,21 @@ static void TestMatrix44(skiatest::Reporter* reporter) {
0, 0, 0, 1);
}
+ test_concat(reporter);
+
if (false) { // avoid bit rot, suppress warning (working on making this pass)
test_common_angles(reporter);
}
-#endif
+
+ test_constructor(reporter);
+ test_gettype(reporter);
+ test_determinant(reporter);
+ test_transpose(reporter);
+ test_get_set_double(reporter);
+ test_set_row_col_major(reporter);
+ test_translate(reporter);
+ test_scale(reporter);
+ test_map2(reporter);
}
#include "TestClassDef.h"
diff --git a/tests/MatrixTest.cpp b/tests/MatrixTest.cpp
index db86f2a3c4..1b8e6661c0 100644
--- a/tests/MatrixTest.cpp
+++ b/tests/MatrixTest.cpp
@@ -98,7 +98,8 @@ static void test_matrix_recttorect(skiatest::Reporter* reporter) {
dst.fRight += SK_Scalar1;
matrix.setRectToRect(src, dst, SkMatrix::kFill_ScaleToFit);
- REPORTER_ASSERT(reporter, SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask == matrix.getType());
+ REPORTER_ASSERT(reporter,
+ (SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask) == matrix.getType());
REPORTER_ASSERT(reporter, matrix.rectStaysRect());
dst = src;
diff --git a/tests/PathTest.cpp b/tests/PathTest.cpp
index 8197e685ff..6f9a9e6465 100644
--- a/tests/PathTest.cpp
+++ b/tests/PathTest.cpp
@@ -18,11 +18,38 @@
#include "SkWriter32.h"
#include "SkSurface.h"
+#if defined(WIN32)
+ #define SUPPRESS_VISIBILITY_WARNING
+#else
+ #define SUPPRESS_VISIBILITY_WARNING __attribute__((visibility("hidden")))
+#endif
+
static SkSurface* new_surface(int w, int h) {
SkImage::Info info = {
w, h, SkImage::kPMColor_ColorType, SkImage::kPremul_AlphaType
};
- return SkSurface::NewRaster(info, NULL);
+ return SkSurface::NewRaster(info);
+}
+
+// Make sure we stay non-finite once we get there (unless we reset or rewind).
+static void test_addrect_isfinite(skiatest::Reporter* reporter) {
+ SkPath path;
+
+ path.addRect(SkRect::MakeWH(50, 100));
+ REPORTER_ASSERT(reporter, path.isFinite());
+
+ path.moveTo(0, 0);
+ path.lineTo(SK_ScalarInfinity, 42);
+ REPORTER_ASSERT(reporter, !path.isFinite());
+
+ path.addRect(SkRect::MakeWH(50, 100));
+ REPORTER_ASSERT(reporter, !path.isFinite());
+
+ path.reset();
+ REPORTER_ASSERT(reporter, path.isFinite());
+
+ path.addRect(SkRect::MakeWH(50, 100));
+ REPORTER_ASSERT(reporter, path.isFinite());
}
// Inspired by http://ie.microsoft.com/testdrive/Performance/Chalkboard/
@@ -77,6 +104,106 @@ static void test_isfinite_after_transform(skiatest::Reporter* reporter) {
REPORTER_ASSERT(reporter, path.getBounds().isEmpty());
}
+static void add_corner_arc(SkPath* path, const SkRect& rect,
+ SkScalar xIn, SkScalar yIn,
+ int startAngle)
+{
+
+ SkScalar rx = SkMinScalar(rect.width(), xIn);
+ SkScalar ry = SkMinScalar(rect.height(), yIn);
+
+ SkRect arcRect;
+ arcRect.set(-rx, -ry, rx, ry);
+ switch (startAngle) {
+ case 0:
+ arcRect.offset(rect.fRight - arcRect.fRight, rect.fBottom - arcRect.fBottom);
+ break;
+ case 90:
+ arcRect.offset(rect.fLeft - arcRect.fLeft, rect.fBottom - arcRect.fBottom);
+ break;
+ case 180:
+ arcRect.offset(rect.fLeft - arcRect.fLeft, rect.fTop - arcRect.fTop);
+ break;
+ case 270:
+ arcRect.offset(rect.fRight - arcRect.fRight, rect.fTop - arcRect.fTop);
+ break;
+ default:
+ break;
+ }
+
+ path->arcTo(arcRect, SkIntToScalar(startAngle), SkIntToScalar(90), false);
+}
+
+static void make_arb_round_rect(SkPath* path, const SkRect& r,
+ SkScalar xCorner, SkScalar yCorner) {
+ // we are lazy here and use the same x & y for each corner
+ add_corner_arc(path, r, xCorner, yCorner, 270);
+ add_corner_arc(path, r, xCorner, yCorner, 0);
+ add_corner_arc(path, r, xCorner, yCorner, 90);
+ add_corner_arc(path, r, xCorner, yCorner, 180);
+ path->close();
+}
+
+// Chrome creates its own round rects with each corner possibly being different.
+// Performance will suffer if they are not convex.
+// Note: PathBench::ArbRoundRectBench performs almost exactly
+// the same test (but with drawing)
+static void test_arb_round_rect_is_convex(skiatest::Reporter* reporter) {
+ SkRandom rand;
+ SkRect r;
+
+ for (int i = 0; i < 5000; ++i) {
+
+ SkScalar size = rand.nextUScalar1() * 30;
+ if (size < SK_Scalar1) {
+ continue;
+ }
+ r.fLeft = rand.nextUScalar1() * 300;
+ r.fTop = rand.nextUScalar1() * 300;
+ r.fRight = r.fLeft + 2 * size;
+ r.fBottom = r.fTop + 2 * size;
+
+ SkPath temp;
+
+ make_arb_round_rect(&temp, r, r.width() / 10, r.height() / 15);
+
+#ifdef SK_REDEFINE_ROOT2OVER2_TO_MAKE_ARCTOS_CONVEX
+ REPORTER_ASSERT(reporter, temp.isConvex());
+#endif
+ }
+}
+
+// Chrome will sometimes create a 0 radius round rect. The degenerate
+// quads prevent the path from being converted to a rect
+// Note: PathBench::ArbRoundRectBench performs almost exactly
+// the same test (but with drawing)
+static void test_arb_zero_rad_round_rect_is_rect(skiatest::Reporter* reporter) {
+ SkRandom rand;
+ SkRect r;
+
+ for (int i = 0; i < 5000; ++i) {
+
+ SkScalar size = rand.nextUScalar1() * 30;
+ if (size < SK_Scalar1) {
+ continue;
+ }
+ r.fLeft = rand.nextUScalar1() * 300;
+ r.fTop = rand.nextUScalar1() * 300;
+ r.fRight = r.fLeft + 2 * size;
+ r.fBottom = r.fTop + 2 * size;
+
+ SkPath temp;
+
+ make_arb_round_rect(&temp, r, 0, 0);
+
+#ifdef SK_REDEFINE_ROOT2OVER2_TO_MAKE_ARCTOS_CONVEX
+ SkRect result;
+ REPORTER_ASSERT(reporter, temp.isRect(&result));
+ REPORTER_ASSERT(reporter, r == result);
+#endif
+ }
+}
+
static void test_rect_isfinite(skiatest::Reporter* reporter) {
const SkScalar inf = SK_ScalarInfinity;
const SkScalar nan = SK_ScalarNaN;
@@ -223,19 +350,23 @@ static void test_strokerec(skiatest::Reporter* reporter) {
REPORTER_ASSERT(reporter, SkStrokeRec::kFill_Style == rec.getStyle());
}
-/**
- * cheapIsDirection can take a shortcut when a path is marked convex.
- * This function ensures that we always test cheapIsDirection when the path
- * is flagged with unknown convexity status.
- */
-static void check_direction(SkPath* path,
- SkPath::Direction expectedDir,
- skiatest::Reporter* reporter) {
- if (SkPath::kConvex_Convexity == path->getConvexity()) {
- REPORTER_ASSERT(reporter, path->cheapIsDirection(expectedDir));
- path->setConvexity(SkPath::kUnknown_Convexity);
+// Set this for paths that don't have a consistent direction such as a bowtie.
+// (cheapComputeDirection is not expected to catch these.)
+static const SkPath::Direction kDontCheckDir = static_cast<SkPath::Direction>(-1);
+
+static void check_direction(skiatest::Reporter* reporter, const SkPath& path,
+ SkPath::Direction expected) {
+ if (expected == kDontCheckDir) {
+ return;
+ }
+ SkPath copy(path); // we make a copy so that we don't cache the result on the passed in path.
+
+ SkPath::Direction dir;
+ if (copy.cheapComputeDirection(&dir)) {
+ REPORTER_ASSERT(reporter, dir == expected);
+ } else {
+ REPORTER_ASSERT(reporter, SkPath::kUnknown_Direction == expected);
}
- REPORTER_ASSERT(reporter, path->cheapIsDirection(expectedDir));
}
static void test_direction(skiatest::Reporter* reporter) {
@@ -244,6 +375,7 @@ static void test_direction(skiatest::Reporter* reporter) {
REPORTER_ASSERT(reporter, !path.cheapComputeDirection(NULL));
REPORTER_ASSERT(reporter, !path.cheapIsDirection(SkPath::kCW_Direction));
REPORTER_ASSERT(reporter, !path.cheapIsDirection(SkPath::kCCW_Direction));
+ REPORTER_ASSERT(reporter, path.cheapIsDirection(SkPath::kUnknown_Direction));
static const char* gDegen[] = {
"M 10 10",
@@ -266,13 +398,14 @@ static void test_direction(skiatest::Reporter* reporter) {
"M 20 10 Q 20 20 30 20 L 10 20", // test double-back at y-max
// rect with top two corners replaced by cubics with identical middle
// control points
- "M 10 10 C 10 0 10 0 20 0 L 40 0 C 50 0 50 0 50 10"
+ "M 10 10 C 10 0 10 0 20 0 L 40 0 C 50 0 50 0 50 10",
+ "M 20 10 L 0 10 Q 10 10 20 0", // left, degenerate serif
};
for (i = 0; i < SK_ARRAY_COUNT(gCW); ++i) {
path.reset();
bool valid = SkParsePath::FromSVGString(gCW[i], &path);
REPORTER_ASSERT(reporter, valid);
- check_direction(&path, SkPath::kCW_Direction, reporter);
+ check_direction(reporter, path, SkPath::kCW_Direction);
}
static const char* gCCW[] = {
@@ -281,13 +414,14 @@ static void test_direction(skiatest::Reporter* reporter) {
"M 20 10 Q 20 20 10 20 L 30 20", // test double-back at y-max
// rect with top two corners replaced by cubics with identical middle
// control points
- "M 50 10 C 50 0 50 0 40 0 L 20 0 C 10 0 10 0 10 10"
+ "M 50 10 C 50 0 50 0 40 0 L 20 0 C 10 0 10 0 10 10",
+ "M 10 10 L 30 10 Q 20 10 10 0", // right, degenerate serif
};
for (i = 0; i < SK_ARRAY_COUNT(gCCW); ++i) {
path.reset();
bool valid = SkParsePath::FromSVGString(gCCW[i], &path);
REPORTER_ASSERT(reporter, valid);
- check_direction(&path, SkPath::kCCW_Direction, reporter);
+ check_direction(reporter, path, SkPath::kCCW_Direction);
}
// Test two donuts, each wound a different direction. Only the outer contour
@@ -295,12 +429,12 @@ static void test_direction(skiatest::Reporter* reporter) {
path.reset();
path.addCircle(0, 0, SkIntToScalar(2), SkPath::kCW_Direction);
path.addCircle(0, 0, SkIntToScalar(1), SkPath::kCCW_Direction);
- check_direction(&path, SkPath::kCW_Direction, reporter);
+ check_direction(reporter, path, SkPath::kCW_Direction);
path.reset();
path.addCircle(0, 0, SkIntToScalar(1), SkPath::kCW_Direction);
path.addCircle(0, 0, SkIntToScalar(2), SkPath::kCCW_Direction);
- check_direction(&path, SkPath::kCCW_Direction, reporter);
+ check_direction(reporter, path, SkPath::kCCW_Direction);
#ifdef SK_SCALAR_IS_FLOAT
// triangle with one point really far from the origin.
@@ -309,7 +443,7 @@ static void test_direction(skiatest::Reporter* reporter) {
path.moveTo(SkFloatToScalar(SkBits2Float(0x501c7652)), SkFloatToScalar(SkBits2Float(0x501c7652)));
path.lineTo(110 * SK_Scalar1, -10 * SK_Scalar1);
path.lineTo(-10 * SK_Scalar1, 60 * SK_Scalar1);
- check_direction(&path, SkPath::kCCW_Direction, reporter);
+ check_direction(reporter, path, SkPath::kCCW_Direction);
#endif
}
@@ -472,7 +606,8 @@ static void test_close(skiatest::Reporter* reporter) {
static void check_convexity(skiatest::Reporter* reporter, const SkPath& path,
SkPath::Convexity expected) {
- SkPath::Convexity c = SkPath::ComputeConvexity(path);
+ SkPath copy(path); // we make a copy so that we don't cache the result on the passed in path.
+ SkPath::Convexity c = copy.getConvexity();
REPORTER_ASSERT(reporter, c == expected);
}
@@ -481,12 +616,14 @@ static void test_convexity2(skiatest::Reporter* reporter) {
pt.moveTo(0, 0);
pt.close();
check_convexity(reporter, pt, SkPath::kConvex_Convexity);
+ check_direction(reporter, pt, SkPath::kUnknown_Direction);
SkPath line;
line.moveTo(12*SK_Scalar1, 20*SK_Scalar1);
line.lineTo(-12*SK_Scalar1, -20*SK_Scalar1);
line.close();
- check_convexity(reporter, pt, SkPath::kConvex_Convexity);
+ check_convexity(reporter, line, SkPath::kConvex_Convexity);
+ check_direction(reporter, line, SkPath::kUnknown_Direction);
SkPath triLeft;
triLeft.moveTo(0, 0);
@@ -494,6 +631,7 @@ static void test_convexity2(skiatest::Reporter* reporter) {
triLeft.lineTo(SK_Scalar1, SK_Scalar1);
triLeft.close();
check_convexity(reporter, triLeft, SkPath::kConvex_Convexity);
+ check_direction(reporter, triLeft, SkPath::kCW_Direction);
SkPath triRight;
triRight.moveTo(0, 0);
@@ -501,6 +639,7 @@ static void test_convexity2(skiatest::Reporter* reporter) {
triRight.lineTo(SK_Scalar1, SK_Scalar1);
triRight.close();
check_convexity(reporter, triRight, SkPath::kConvex_Convexity);
+ check_direction(reporter, triRight, SkPath::kCCW_Direction);
SkPath square;
square.moveTo(0, 0);
@@ -509,6 +648,7 @@ static void test_convexity2(skiatest::Reporter* reporter) {
square.lineTo(0, SK_Scalar1);
square.close();
check_convexity(reporter, square, SkPath::kConvex_Convexity);
+ check_direction(reporter, square, SkPath::kCW_Direction);
SkPath redundantSquare;
redundantSquare.moveTo(0, 0);
@@ -525,6 +665,7 @@ static void test_convexity2(skiatest::Reporter* reporter) {
redundantSquare.lineTo(0, SK_Scalar1);
redundantSquare.close();
check_convexity(reporter, redundantSquare, SkPath::kConvex_Convexity);
+ check_direction(reporter, redundantSquare, SkPath::kCW_Direction);
SkPath bowTie;
bowTie.moveTo(0, 0);
@@ -541,6 +682,7 @@ static void test_convexity2(skiatest::Reporter* reporter) {
bowTie.lineTo(0, SK_Scalar1);
bowTie.close();
check_convexity(reporter, bowTie, SkPath::kConcave_Convexity);
+ check_direction(reporter, bowTie, kDontCheckDir);
SkPath spiral;
spiral.moveTo(0, 0);
@@ -552,6 +694,7 @@ static void test_convexity2(skiatest::Reporter* reporter) {
spiral.lineTo(50*SK_Scalar1, 75*SK_Scalar1);
spiral.close();
check_convexity(reporter, spiral, SkPath::kConcave_Convexity);
+ check_direction(reporter, spiral, kDontCheckDir);
SkPath dent;
dent.moveTo(0, 0);
@@ -561,6 +704,7 @@ static void test_convexity2(skiatest::Reporter* reporter) {
dent.lineTo(-200*SK_Scalar1, 100*SK_Scalar1);
dent.close();
check_convexity(reporter, dent, SkPath::kConcave_Convexity);
+ check_direction(reporter, dent, SkPath::kCW_Direction);
}
static void check_convex_bounds(skiatest::Reporter* reporter, const SkPath& p,
@@ -598,44 +742,44 @@ static void setFromString(SkPath* path, const char str[]) {
}
static void test_convexity(skiatest::Reporter* reporter) {
- static const SkPath::Convexity C = SkPath::kConcave_Convexity;
- static const SkPath::Convexity V = SkPath::kConvex_Convexity;
-
SkPath path;
- REPORTER_ASSERT(reporter, V == SkPath::ComputeConvexity(path));
+ check_convexity(reporter, path, SkPath::kConvex_Convexity);
path.addCircle(0, 0, SkIntToScalar(10));
- REPORTER_ASSERT(reporter, V == SkPath::ComputeConvexity(path));
+ check_convexity(reporter, path, SkPath::kConvex_Convexity);
path.addCircle(0, 0, SkIntToScalar(10)); // 2nd circle
- REPORTER_ASSERT(reporter, C == SkPath::ComputeConvexity(path));
+ check_convexity(reporter, path, SkPath::kConcave_Convexity);
+
path.reset();
path.addRect(0, 0, SkIntToScalar(10), SkIntToScalar(10), SkPath::kCCW_Direction);
- REPORTER_ASSERT(reporter, V == SkPath::ComputeConvexity(path));
+ check_convexity(reporter, path, SkPath::kConvex_Convexity);
REPORTER_ASSERT(reporter, path.cheapIsDirection(SkPath::kCCW_Direction));
+
path.reset();
path.addRect(0, 0, SkIntToScalar(10), SkIntToScalar(10), SkPath::kCW_Direction);
- REPORTER_ASSERT(reporter, V == SkPath::ComputeConvexity(path));
+ check_convexity(reporter, path, SkPath::kConvex_Convexity);
REPORTER_ASSERT(reporter, path.cheapIsDirection(SkPath::kCW_Direction));
static const struct {
const char* fPathStr;
SkPath::Convexity fExpectedConvexity;
+ SkPath::Direction fExpectedDirection;
} gRec[] = {
- { "", SkPath::kConvex_Convexity },
- { "0 0", SkPath::kConvex_Convexity },
- { "0 0 10 10", SkPath::kConvex_Convexity },
- { "0 0 10 10 20 20 0 0 10 10", SkPath::kConcave_Convexity },
- { "0 0 10 10 10 20", SkPath::kConvex_Convexity },
- { "0 0 10 10 10 0", SkPath::kConvex_Convexity },
- { "0 0 10 10 10 0 0 10", SkPath::kConcave_Convexity },
- { "0 0 10 0 0 10 -10 -10", SkPath::kConcave_Convexity },
+ { "", SkPath::kConvex_Convexity, SkPath::kUnknown_Direction },
+ { "0 0", SkPath::kConvex_Convexity, SkPath::kUnknown_Direction },
+ { "0 0 10 10", SkPath::kConvex_Convexity, SkPath::kUnknown_Direction },
+ { "0 0 10 10 20 20 0 0 10 10", SkPath::kConcave_Convexity, SkPath::kUnknown_Direction },
+ { "0 0 10 10 10 20", SkPath::kConvex_Convexity, SkPath::kCW_Direction },
+ { "0 0 10 10 10 0", SkPath::kConvex_Convexity, SkPath::kCCW_Direction },
+ { "0 0 10 10 10 0 0 10", SkPath::kConcave_Convexity, kDontCheckDir },
+ { "0 0 10 0 0 10 -10 -10", SkPath::kConcave_Convexity, SkPath::kCW_Direction },
};
for (size_t i = 0; i < SK_ARRAY_COUNT(gRec); ++i) {
SkPath path;
setFromString(&path, gRec[i].fPathStr);
- SkPath::Convexity c = SkPath::ComputeConvexity(path);
- REPORTER_ASSERT(reporter, c == gRec[i].fExpectedConvexity);
+ check_convexity(reporter, path, gRec[i].fExpectedConvexity);
+ check_direction(reporter, path, gRec[i].fExpectedDirection);
}
}
@@ -685,6 +829,162 @@ static void test_isLine(skiatest::Reporter* reporter) {
REPORTER_ASSERT(reporter, pts[1].equals(lineX, lineY));
}
+static void test_conservativelyContains(skiatest::Reporter* reporter) {
+ SkPath path;
+
+ // kBaseRect is used to construct most our test paths: a rect, a circle, and a round-rect.
+ static const SkRect kBaseRect = SkRect::MakeWH(SkIntToScalar(100), SkIntToScalar(100));
+
+ // A circle that bounds kBaseRect (with a significant amount of slop)
+ SkScalar circleR = SkMaxScalar(kBaseRect.width(), kBaseRect.height());
+ circleR = SkScalarMul(circleR, SkFloatToScalar(1.75f)) / 2;
+ static const SkPoint kCircleC = {kBaseRect.centerX(), kBaseRect.centerY()};
+
+ // round-rect radii
+ static const SkScalar kRRRadii[] = {SkIntToScalar(5), SkIntToScalar(3)};
+
+ static const struct SUPPRESS_VISIBILITY_WARNING {
+ SkRect fQueryRect;
+ bool fInRect;
+ bool fInCircle;
+ bool fInRR;
+ } kQueries[] = {
+ {kBaseRect, true, true, false},
+
+ // rect well inside of kBaseRect
+ {SkRect::MakeLTRB(kBaseRect.fLeft + SkFloatToScalar(0.25f)*kBaseRect.width(),
+ kBaseRect.fTop + SkFloatToScalar(0.25f)*kBaseRect.height(),
+ kBaseRect.fRight - SkFloatToScalar(0.25f)*kBaseRect.width(),
+ kBaseRect.fBottom - SkFloatToScalar(0.25f)*kBaseRect.height()),
+ true, true, true},
+
+ // rects with edges off by one from kBaseRect's edges
+ {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop,
+ kBaseRect.width(), kBaseRect.height() + 1),
+ false, true, false},
+ {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop,
+ kBaseRect.width() + 1, kBaseRect.height()),
+ false, true, false},
+ {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop,
+ kBaseRect.width() + 1, kBaseRect.height() + 1),
+ false, true, false},
+ {SkRect::MakeXYWH(kBaseRect.fLeft - 1, kBaseRect.fTop,
+ kBaseRect.width(), kBaseRect.height()),
+ false, true, false},
+ {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop - 1,
+ kBaseRect.width(), kBaseRect.height()),
+ false, true, false},
+ {SkRect::MakeXYWH(kBaseRect.fLeft - 1, kBaseRect.fTop,
+ kBaseRect.width() + 2, kBaseRect.height()),
+ false, true, false},
+ {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop - 1,
+ kBaseRect.width() + 2, kBaseRect.height()),
+ false, true, false},
+
+ // zero-w/h rects at each corner of kBaseRect
+ {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop, 0, 0), true, true, false},
+ {SkRect::MakeXYWH(kBaseRect.fRight, kBaseRect.fTop, 0, 0), true, true, false},
+ {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fBottom, 0, 0), true, true, false},
+ {SkRect::MakeXYWH(kBaseRect.fRight, kBaseRect.fBottom, 0, 0), true, true, false},
+
+ // far away rect
+ {SkRect::MakeXYWH(10 * kBaseRect.fRight, 10 * kBaseRect.fBottom,
+ SkIntToScalar(10), SkIntToScalar(10)),
+ false, false, false},
+
+ // very large rect containing kBaseRect
+ {SkRect::MakeXYWH(kBaseRect.fLeft - 5 * kBaseRect.width(),
+ kBaseRect.fTop - 5 * kBaseRect.height(),
+ 11 * kBaseRect.width(), 11 * kBaseRect.height()),
+ false, false, false},
+
+ // skinny rect that spans same y-range as kBaseRect
+ {SkRect::MakeXYWH(kBaseRect.centerX(), kBaseRect.fTop,
+ SkIntToScalar(1), kBaseRect.height()),
+ true, true, true},
+
+ // short rect that spans same x-range as kBaseRect
+ {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.centerY(), kBaseRect.width(), SkScalar(1)),
+ true, true, true},
+
+ // skinny rect that spans slightly larger y-range than kBaseRect
+ {SkRect::MakeXYWH(kBaseRect.centerX(), kBaseRect.fTop,
+ SkIntToScalar(1), kBaseRect.height() + 1),
+ false, true, false},
+
+ // short rect that spans slightly larger x-range than kBaseRect
+ {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.centerY(),
+ kBaseRect.width() + 1, SkScalar(1)),
+ false, true, false},
+ };
+
+ for (int inv = 0; inv < 4; ++inv) {
+ for (size_t q = 0; q < SK_ARRAY_COUNT(kQueries); ++q) {
+ SkRect qRect = kQueries[q].fQueryRect;
+ if (inv & 0x1) {
+ SkTSwap(qRect.fLeft, qRect.fRight);
+ }
+ if (inv & 0x2) {
+ SkTSwap(qRect.fTop, qRect.fBottom);
+ }
+ for (int d = 0; d < 2; ++d) {
+ SkPath::Direction dir = d ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
+ path.reset();
+ path.addRect(kBaseRect, dir);
+ REPORTER_ASSERT(reporter, kQueries[q].fInRect ==
+ path.conservativelyContainsRect(qRect));
+
+ path.reset();
+ path.addCircle(kCircleC.fX, kCircleC.fY, circleR, dir);
+ REPORTER_ASSERT(reporter, kQueries[q].fInCircle ==
+ path.conservativelyContainsRect(qRect));
+
+ path.reset();
+ path.addRoundRect(kBaseRect, kRRRadii[0], kRRRadii[1], dir);
+ REPORTER_ASSERT(reporter, kQueries[q].fInRR ==
+ path.conservativelyContainsRect(qRect));
+ }
+ // Slightly non-convex shape, shouldn't contain any rects.
+ path.reset();
+ path.moveTo(0, 0);
+ path.lineTo(SkIntToScalar(50), SkFloatToScalar(0.05f));
+ path.lineTo(SkIntToScalar(100), 0);
+ path.lineTo(SkIntToScalar(100), SkIntToScalar(100));
+ path.lineTo(0, SkIntToScalar(100));
+ path.close();
+ REPORTER_ASSERT(reporter, !path.conservativelyContainsRect(qRect));
+ }
+ }
+
+ // make sure a minimal convex shape works, a right tri with edges along pos x and y axes.
+ path.reset();
+ path.moveTo(0, 0);
+ path.lineTo(SkIntToScalar(100), 0);
+ path.lineTo(0, SkIntToScalar(100));
+
+ // inside, on along top edge
+ REPORTER_ASSERT(reporter, path.conservativelyContainsRect(SkRect::MakeXYWH(SkIntToScalar(50), 0,
+ SkIntToScalar(10),
+ SkIntToScalar(10))));
+ // above
+ REPORTER_ASSERT(reporter, !path.conservativelyContainsRect(
+ SkRect::MakeXYWH(SkIntToScalar(50),
+ SkIntToScalar(-10),
+ SkIntToScalar(10),
+ SkIntToScalar(10))));
+ // to the left
+ REPORTER_ASSERT(reporter, !path.conservativelyContainsRect(SkRect::MakeXYWH(SkIntToScalar(-10),
+ SkIntToScalar(5),
+ SkIntToScalar(5),
+ SkIntToScalar(5))));
+
+ // outside the diagonal edge
+ REPORTER_ASSERT(reporter, !path.conservativelyContainsRect(SkRect::MakeXYWH(SkIntToScalar(10),
+ SkIntToScalar(200),
+ SkIntToScalar(20),
+ SkIntToScalar(5))));
+}
+
// Simple isRect test is inline TestPath, below.
// test_isRect provides more extensive testing.
static void test_isRect(skiatest::Reporter* reporter) {
@@ -705,6 +1005,7 @@ static void test_isRect(skiatest::Reporter* reporter) {
SkPoint rc[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}, {0, 0}};
SkPoint rd[] = {{0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}};
SkPoint re[] = {{0, 0}, {1, 0}, {1, 0}, {1, 1}, {0, 1}};
+ SkPoint rf[] = {{1, 0}, {8, 0}, {8, 8}, {0, 8}, {0, 0}};
// failing tests
SkPoint f1[] = {{0, 0}, {1, 0}, {1, 1}}; // too few points
@@ -715,6 +1016,9 @@ static void test_isRect(skiatest::Reporter* reporter) {
SkPoint f6[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}, {0, 2}}; // end overshoots
SkPoint f7[] = {{0, 0}, {1, 0}, {1, 1}, {0, 2}}; // end overshoots
SkPoint f8[] = {{0, 0}, {1, 0}, {1, 1}, {1, 0}}; // 'L'
+ SkPoint f9[] = {{1, 0}, {8, 0}, {8, 8}, {0, 8}, {0, 0}, {2, 0}}; // overlaps
+ SkPoint fa[] = {{1, 0}, {8, 0}, {8, 8}, {0, 8}, {0, -1}, {1, -1}}; // non colinear gap
+ SkPoint fb[] = {{1, 0}, {8, 0}, {8, 8}, {0, 8}, {0, 1}}; // falls short
// failing, no close
SkPoint c1[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}}; // close doesn't match
@@ -723,18 +1027,18 @@ static void test_isRect(skiatest::Reporter* reporter) {
size_t testLen[] = {
sizeof(r1), sizeof(r2), sizeof(r3), sizeof(r4), sizeof(r5), sizeof(r6),
sizeof(r7), sizeof(r8), sizeof(r9), sizeof(ra), sizeof(rb), sizeof(rc),
- sizeof(rd), sizeof(re),
+ sizeof(rd), sizeof(re), sizeof(rf),
sizeof(f1), sizeof(f2), sizeof(f3), sizeof(f4), sizeof(f5), sizeof(f6),
- sizeof(f7), sizeof(f8),
+ sizeof(f7), sizeof(f8), sizeof(f9), sizeof(fa), sizeof(fb),
sizeof(c1), sizeof(c2)
};
SkPoint* tests[] = {
- r1, r2, r3, r4, r5, r6, r7, r8, r9, ra, rb, rc, rd, re,
- f1, f2, f3, f4, f5, f6, f7, f8,
+ r1, r2, r3, r4, r5, r6, r7, r8, r9, ra, rb, rc, rd, re, rf,
+ f1, f2, f3, f4, f5, f6, f7, f8, f9, fa, fb,
c1, c2
};
- SkPoint* lastPass = re;
- SkPoint* lastClose = f8;
+ SkPoint* lastPass = rf;
+ SkPoint* lastClose = fb;
bool fail = false;
bool close = true;
const size_t testCount = sizeof(tests) / sizeof(tests[0]);
@@ -749,6 +1053,34 @@ static void test_isRect(skiatest::Reporter* reporter) {
path.close();
}
REPORTER_ASSERT(reporter, fail ^ path.isRect(0));
+ REPORTER_ASSERT(reporter, fail ^ path.isRect(NULL, NULL));
+
+ if (!fail) {
+ SkRect computed, expected;
+ expected.set(tests[testIndex], testLen[testIndex] / sizeof(SkPoint));
+ REPORTER_ASSERT(reporter, path.isRect(&computed));
+ REPORTER_ASSERT(reporter, expected == computed);
+
+ bool isClosed;
+ SkPath::Direction direction, cheapDirection;
+ REPORTER_ASSERT(reporter, path.cheapComputeDirection(&cheapDirection));
+ REPORTER_ASSERT(reporter, path.isRect(&isClosed, &direction));
+ REPORTER_ASSERT(reporter, isClosed == close);
+ REPORTER_ASSERT(reporter, direction == cheapDirection);
+ } else {
+ SkRect computed;
+ computed.set(123, 456, 789, 1011);
+ REPORTER_ASSERT(reporter, !path.isRect(&computed));
+ REPORTER_ASSERT(reporter, computed.fLeft == 123 && computed.fTop == 456);
+ REPORTER_ASSERT(reporter, computed.fRight == 789 && computed.fBottom == 1011);
+
+ bool isClosed = (bool) -1;
+ SkPath::Direction direction = (SkPath::Direction) -1;
+ REPORTER_ASSERT(reporter, !path.isRect(&isClosed, &direction));
+ REPORTER_ASSERT(reporter, isClosed == (bool) -1);
+ REPORTER_ASSERT(reporter, direction == (SkPath::Direction) -1);
+ }
+
if (tests[testIndex] == lastPass) {
fail = true;
}
@@ -813,6 +1145,195 @@ static void test_isRect(skiatest::Reporter* reporter) {
REPORTER_ASSERT(reporter, fail ^ path1.isRect(0));
}
+static void test_isNestedRects(skiatest::Reporter* reporter) {
+ // passing tests (all moveTo / lineTo...
+ SkPoint r1[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}};
+ SkPoint r2[] = {{1, 0}, {1, 1}, {0, 1}, {0, 0}};
+ SkPoint r3[] = {{1, 1}, {0, 1}, {0, 0}, {1, 0}};
+ SkPoint r4[] = {{0, 1}, {0, 0}, {1, 0}, {1, 1}};
+ SkPoint r5[] = {{0, 0}, {0, 1}, {1, 1}, {1, 0}};
+ SkPoint r6[] = {{0, 1}, {1, 1}, {1, 0}, {0, 0}};
+ SkPoint r7[] = {{1, 1}, {1, 0}, {0, 0}, {0, 1}};
+ SkPoint r8[] = {{1, 0}, {0, 0}, {0, 1}, {1, 1}};
+ SkPoint r9[] = {{0, 1}, {1, 1}, {1, 0}, {0, 0}};
+ SkPoint ra[] = {{0, 0}, {0, .5f}, {0, 1}, {.5f, 1}, {1, 1}, {1, .5f},
+ {1, 0}, {.5f, 0}};
+ SkPoint rb[] = {{0, 0}, {.5f, 0}, {1, 0}, {1, .5f}, {1, 1}, {.5f, 1},
+ {0, 1}, {0, .5f}};
+ SkPoint rc[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}, {0, 0}};
+ SkPoint rd[] = {{0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}};
+ SkPoint re[] = {{0, 0}, {1, 0}, {1, 0}, {1, 1}, {0, 1}};
+
+ // failing tests
+ SkPoint f1[] = {{0, 0}, {1, 0}, {1, 1}}; // too few points
+ SkPoint f2[] = {{0, 0}, {1, 1}, {0, 1}, {1, 0}}; // diagonal
+ SkPoint f3[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}, {0, 0}, {1, 0}}; // wraps
+ SkPoint f4[] = {{0, 0}, {1, 0}, {0, 0}, {1, 0}, {1, 1}, {0, 1}}; // backs up
+ SkPoint f5[] = {{0, 0}, {1, 0}, {1, 1}, {2, 0}}; // end overshoots
+ SkPoint f6[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}, {0, 2}}; // end overshoots
+ SkPoint f7[] = {{0, 0}, {1, 0}, {1, 1}, {0, 2}}; // end overshoots
+ SkPoint f8[] = {{0, 0}, {1, 0}, {1, 1}, {1, 0}}; // 'L'
+
+ // failing, no close
+ SkPoint c1[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}}; // close doesn't match
+ SkPoint c2[] = {{0, 0}, {1, 0}, {1, 2}, {0, 2}, {0, 1}}; // ditto
+
+ size_t testLen[] = {
+ sizeof(r1), sizeof(r2), sizeof(r3), sizeof(r4), sizeof(r5), sizeof(r6),
+ sizeof(r7), sizeof(r8), sizeof(r9), sizeof(ra), sizeof(rb), sizeof(rc),
+ sizeof(rd), sizeof(re),
+ sizeof(f1), sizeof(f2), sizeof(f3), sizeof(f4), sizeof(f5), sizeof(f6),
+ sizeof(f7), sizeof(f8),
+ sizeof(c1), sizeof(c2)
+ };
+ SkPoint* tests[] = {
+ r1, r2, r3, r4, r5, r6, r7, r8, r9, ra, rb, rc, rd, re,
+ f1, f2, f3, f4, f5, f6, f7, f8,
+ c1, c2
+ };
+ const SkPoint* lastPass = re;
+ const SkPoint* lastClose = f8;
+ const size_t testCount = sizeof(tests) / sizeof(tests[0]);
+ size_t index;
+ for (int rectFirst = 0; rectFirst <= 1; ++rectFirst) {
+ bool fail = false;
+ bool close = true;
+ for (size_t testIndex = 0; testIndex < testCount; ++testIndex) {
+ SkPath path;
+ if (rectFirst) {
+ path.addRect(-1, -1, 2, 2, SkPath::kCW_Direction);
+ }
+ path.moveTo(tests[testIndex][0].fX, tests[testIndex][0].fY);
+ for (index = 1; index < testLen[testIndex] / sizeof(SkPoint); ++index) {
+ path.lineTo(tests[testIndex][index].fX, tests[testIndex][index].fY);
+ }
+ if (close) {
+ path.close();
+ }
+ if (!rectFirst) {
+ path.addRect(-1, -1, 2, 2, SkPath::kCCW_Direction);
+ }
+ REPORTER_ASSERT(reporter, fail ^ path.isNestedRects(0));
+ if (!fail) {
+ SkRect expected[2], computed[2];
+ SkRect testBounds;
+ testBounds.set(tests[testIndex], testLen[testIndex] / sizeof(SkPoint));
+ expected[0] = SkRect::MakeLTRB(-1, -1, 2, 2);
+ expected[1] = testBounds;
+ REPORTER_ASSERT(reporter, path.isNestedRects(computed));
+ REPORTER_ASSERT(reporter, expected[0] == computed[0]);
+ REPORTER_ASSERT(reporter, expected[1] == computed[1]);
+ }
+ if (tests[testIndex] == lastPass) {
+ fail = true;
+ }
+ if (tests[testIndex] == lastClose) {
+ close = false;
+ }
+ }
+
+ // fail, close then line
+ SkPath path1;
+ if (rectFirst) {
+ path1.addRect(-1, -1, 2, 2, SkPath::kCW_Direction);
+ }
+ path1.moveTo(r1[0].fX, r1[0].fY);
+ for (index = 1; index < testLen[0] / sizeof(SkPoint); ++index) {
+ path1.lineTo(r1[index].fX, r1[index].fY);
+ }
+ path1.close();
+ path1.lineTo(1, 0);
+ if (!rectFirst) {
+ path1.addRect(-1, -1, 2, 2, SkPath::kCCW_Direction);
+ }
+ REPORTER_ASSERT(reporter, fail ^ path1.isNestedRects(0));
+
+ // fail, move in the middle
+ path1.reset();
+ if (rectFirst) {
+ path1.addRect(-1, -1, 2, 2, SkPath::kCW_Direction);
+ }
+ path1.moveTo(r1[0].fX, r1[0].fY);
+ for (index = 1; index < testLen[0] / sizeof(SkPoint); ++index) {
+ if (index == 2) {
+ path1.moveTo(1, .5f);
+ }
+ path1.lineTo(r1[index].fX, r1[index].fY);
+ }
+ path1.close();
+ if (!rectFirst) {
+ path1.addRect(-1, -1, 2, 2, SkPath::kCCW_Direction);
+ }
+ REPORTER_ASSERT(reporter, fail ^ path1.isNestedRects(0));
+
+ // fail, move on the edge
+ path1.reset();
+ if (rectFirst) {
+ path1.addRect(-1, -1, 2, 2, SkPath::kCW_Direction);
+ }
+ for (index = 1; index < testLen[0] / sizeof(SkPoint); ++index) {
+ path1.moveTo(r1[index - 1].fX, r1[index - 1].fY);
+ path1.lineTo(r1[index].fX, r1[index].fY);
+ }
+ path1.close();
+ if (!rectFirst) {
+ path1.addRect(-1, -1, 2, 2, SkPath::kCCW_Direction);
+ }
+ REPORTER_ASSERT(reporter, fail ^ path1.isNestedRects(0));
+
+ // fail, quad
+ path1.reset();
+ if (rectFirst) {
+ path1.addRect(-1, -1, 2, 2, SkPath::kCW_Direction);
+ }
+ path1.moveTo(r1[0].fX, r1[0].fY);
+ for (index = 1; index < testLen[0] / sizeof(SkPoint); ++index) {
+ if (index == 2) {
+ path1.quadTo(1, .5f, 1, .5f);
+ }
+ path1.lineTo(r1[index].fX, r1[index].fY);
+ }
+ path1.close();
+ if (!rectFirst) {
+ path1.addRect(-1, -1, 2, 2, SkPath::kCCW_Direction);
+ }
+ REPORTER_ASSERT(reporter, fail ^ path1.isNestedRects(0));
+
+ // fail, cubic
+ path1.reset();
+ if (rectFirst) {
+ path1.addRect(-1, -1, 2, 2, SkPath::kCW_Direction);
+ }
+ path1.moveTo(r1[0].fX, r1[0].fY);
+ for (index = 1; index < testLen[0] / sizeof(SkPoint); ++index) {
+ if (index == 2) {
+ path1.cubicTo(1, .5f, 1, .5f, 1, .5f);
+ }
+ path1.lineTo(r1[index].fX, r1[index].fY);
+ }
+ path1.close();
+ if (!rectFirst) {
+ path1.addRect(-1, -1, 2, 2, SkPath::kCCW_Direction);
+ }
+ REPORTER_ASSERT(reporter, fail ^ path1.isNestedRects(0));
+
+ // fail, not nested
+ path1.reset();
+ path1.addRect(1, 1, 3, 3, SkPath::kCW_Direction);
+ path1.addRect(2, 2, 4, 4, SkPath::kCW_Direction);
+ REPORTER_ASSERT(reporter, fail ^ path1.isNestedRects(0));
+ }
+
+ // pass, stroke rect
+ SkPath src, dst;
+ src.addRect(1, 1, 7, 7, SkPath::kCW_Direction);
+ SkPaint strokePaint;
+ strokePaint.setStyle(SkPaint::kStroke_Style);
+ strokePaint.setStrokeWidth(2);
+ strokePaint.getFillPath(src, &dst);
+ REPORTER_ASSERT(reporter, dst.isNestedRects(0));
+}
+
static void write_and_read_back(skiatest::Reporter* reporter,
const SkPath& p) {
SkWriter32 writer(100);
@@ -915,7 +1436,7 @@ static void test_zero_length_paths(skiatest::Reporter* reporter) {
SkPath p;
uint8_t verbs[32];
- struct zeroPathTestData {
+ struct SUPPRESS_VISIBILITY_WARNING zeroPathTestData {
const char* testPath;
const size_t numResultPts;
const SkRect resultBound;
@@ -1331,33 +1852,46 @@ static void test_raw_iter(skiatest::Reporter* reporter) {
}
static void check_for_circle(skiatest::Reporter* reporter,
- const SkPath& path, bool expected) {
+ const SkPath& path,
+ bool expectedCircle,
+ SkPath::Direction expectedDir) {
SkRect rect;
- REPORTER_ASSERT(reporter, path.isOval(&rect) == expected);
- if (expected) {
+ REPORTER_ASSERT(reporter, path.isOval(&rect) == expectedCircle);
+ REPORTER_ASSERT(reporter, path.cheapIsDirection(expectedDir));
+
+ if (expectedCircle) {
REPORTER_ASSERT(reporter, rect.height() == rect.width());
}
}
static void test_circle_skew(skiatest::Reporter* reporter,
- const SkPath& path) {
+ const SkPath& path,
+ SkPath::Direction dir) {
SkPath tmp;
SkMatrix m;
m.setSkew(SkIntToScalar(3), SkIntToScalar(5));
path.transform(m, &tmp);
- check_for_circle(reporter, tmp, false);
+ // this matrix reverses the direction.
+ if (SkPath::kCCW_Direction == dir) {
+ dir = SkPath::kCW_Direction;
+ } else {
+ SkASSERT(SkPath::kCW_Direction == dir);
+ dir = SkPath::kCCW_Direction;
+ }
+ check_for_circle(reporter, tmp, false, dir);
}
static void test_circle_translate(skiatest::Reporter* reporter,
- const SkPath& path) {
+ const SkPath& path,
+ SkPath::Direction dir) {
SkPath tmp;
// translate at small offset
SkMatrix m;
m.setTranslate(SkIntToScalar(15), SkIntToScalar(15));
path.transform(m, &tmp);
- check_for_circle(reporter, tmp, true);
+ check_for_circle(reporter, tmp, true, dir);
tmp.reset();
m.reset();
@@ -1365,47 +1899,102 @@ static void test_circle_translate(skiatest::Reporter* reporter,
// translate at a relatively big offset
m.setTranslate(SkIntToScalar(1000), SkIntToScalar(1000));
path.transform(m, &tmp);
- check_for_circle(reporter, tmp, true);
+ check_for_circle(reporter, tmp, true, dir);
}
static void test_circle_rotate(skiatest::Reporter* reporter,
- const SkPath& path) {
+ const SkPath& path,
+ SkPath::Direction dir) {
for (int angle = 0; angle < 360; ++angle) {
SkPath tmp;
SkMatrix m;
m.setRotate(SkIntToScalar(angle));
path.transform(m, &tmp);
- // TODO: a rotated circle whose rotated angle is not a mutiple of 90
+ // TODO: a rotated circle whose rotated angle is not a multiple of 90
// degrees is not an oval anymore, this can be improved. we made this
// for the simplicity of our implementation.
if (angle % 90 == 0) {
- check_for_circle(reporter, tmp, true);
+ check_for_circle(reporter, tmp, true, dir);
} else {
- check_for_circle(reporter, tmp, false);
+ check_for_circle(reporter, tmp, false, dir);
}
}
}
+static void test_circle_mirror_x(skiatest::Reporter* reporter,
+ const SkPath& path,
+ SkPath::Direction dir) {
+ SkPath tmp;
+ SkMatrix m;
+ m.reset();
+ m.setScaleX(-SK_Scalar1);
+ path.transform(m, &tmp);
+
+ if (SkPath::kCW_Direction == dir) {
+ dir = SkPath::kCCW_Direction;
+ } else {
+ SkASSERT(SkPath::kCCW_Direction == dir);
+ dir = SkPath::kCW_Direction;
+ }
+
+ check_for_circle(reporter, tmp, true, dir);
+}
+
+static void test_circle_mirror_y(skiatest::Reporter* reporter,
+ const SkPath& path,
+ SkPath::Direction dir) {
+ SkPath tmp;
+ SkMatrix m;
+ m.reset();
+ m.setScaleY(-SK_Scalar1);
+ path.transform(m, &tmp);
+
+ if (SkPath::kCW_Direction == dir) {
+ dir = SkPath::kCCW_Direction;
+ } else {
+ SkASSERT(SkPath::kCCW_Direction == dir);
+ dir = SkPath::kCW_Direction;
+ }
+
+ check_for_circle(reporter, tmp, true, dir);
+}
+
+static void test_circle_mirror_xy(skiatest::Reporter* reporter,
+ const SkPath& path,
+ SkPath::Direction dir) {
+ SkPath tmp;
+ SkMatrix m;
+ m.reset();
+ m.setScaleX(-SK_Scalar1);
+ m.setScaleY(-SK_Scalar1);
+ path.transform(m, &tmp);
+
+ check_for_circle(reporter, tmp, true, dir);
+}
+
static void test_circle_with_direction(skiatest::Reporter* reporter,
SkPath::Direction dir) {
SkPath path;
// circle at origin
path.addCircle(0, 0, SkIntToScalar(20), dir);
- check_for_circle(reporter, path, true);
- test_circle_rotate(reporter, path);
- test_circle_translate(reporter, path);
- test_circle_skew(reporter, path);
+ check_for_circle(reporter, path, true, dir);
+ test_circle_rotate(reporter, path, dir);
+ test_circle_translate(reporter, path, dir);
+ test_circle_skew(reporter, path, dir);
// circle at an offset at (10, 10)
path.reset();
path.addCircle(SkIntToScalar(10), SkIntToScalar(10),
SkIntToScalar(20), dir);
- check_for_circle(reporter, path, true);
- test_circle_rotate(reporter, path);
- test_circle_translate(reporter, path);
- test_circle_skew(reporter, path);
+ check_for_circle(reporter, path, true, dir);
+ test_circle_rotate(reporter, path, dir);
+ test_circle_translate(reporter, path, dir);
+ test_circle_skew(reporter, path, dir);
+ test_circle_mirror_x(reporter, path, dir);
+ test_circle_mirror_y(reporter, path, dir);
+ test_circle_mirror_xy(reporter, path, dir);
}
static void test_circle_with_add_paths(skiatest::Reporter* reporter) {
@@ -1414,7 +2003,10 @@ static void test_circle_with_add_paths(skiatest::Reporter* reporter) {
SkPath rect;
SkPath empty;
- circle.addCircle(0, 0, SkIntToScalar(10), SkPath::kCW_Direction);
+ static const SkPath::Direction kCircleDir = SkPath::kCW_Direction;
+ static const SkPath::Direction kCircleDirOpposite = SkPath::kCCW_Direction;
+
+ circle.addCircle(0, 0, SkIntToScalar(10), kCircleDir);
rect.addRect(SkIntToScalar(5), SkIntToScalar(5),
SkIntToScalar(20), SkIntToScalar(20), SkPath::kCW_Direction);
@@ -1427,17 +2019,17 @@ static void test_circle_with_add_paths(skiatest::Reporter* reporter) {
// empty + circle (translate)
path = empty;
path.addPath(circle, translate);
- check_for_circle(reporter, path, false);
+ check_for_circle(reporter, path, false, kCircleDir);
// circle + empty (translate)
path = circle;
path.addPath(empty, translate);
- check_for_circle(reporter, path, false);
+ check_for_circle(reporter, path, false, kCircleDir);
// test reverseAddPath
path = circle;
path.reverseAddPath(rect);
- check_for_circle(reporter, path, false);
+ check_for_circle(reporter, path, false, kCircleDirOpposite);
}
static void test_circle(skiatest::Reporter* reporter) {
@@ -1448,19 +2040,19 @@ static void test_circle(skiatest::Reporter* reporter) {
SkPath path;
path.addCircle(0, 0, SkIntToScalar(10), SkPath::kCW_Direction);
path.addCircle(0, 0, SkIntToScalar(20), SkPath::kCW_Direction);
- check_for_circle(reporter, path, false);
+ check_for_circle(reporter, path, false, SkPath::kCW_Direction);
// some extra lineTo() would make isOval() fail
path.reset();
path.addCircle(0, 0, SkIntToScalar(10), SkPath::kCW_Direction);
path.lineTo(0, 0);
- check_for_circle(reporter, path, false);
+ check_for_circle(reporter, path, false, SkPath::kCW_Direction);
// not back to the original point
path.reset();
path.addCircle(0, 0, SkIntToScalar(10), SkPath::kCW_Direction);
path.setLastPt(SkIntToScalar(5), SkIntToScalar(5));
- check_for_circle(reporter, path, false);
+ check_for_circle(reporter, path, false, SkPath::kCW_Direction);
test_circle_with_add_paths(reporter);
}
@@ -1598,10 +2190,12 @@ static void TestPath(skiatest::Reporter* reporter) {
test_isLine(reporter);
test_isRect(reporter);
+ test_isNestedRects(reporter);
test_zero_length_paths(reporter);
test_direction(reporter);
test_convexity(reporter);
test_convexity2(reporter);
+ test_conservativelyContains(reporter);
test_close(reporter);
test_segment_masks(reporter);
test_flattening(reporter);
@@ -1616,6 +2210,9 @@ static void TestPath(skiatest::Reporter* reporter) {
test_isfinite(reporter);
test_isfinite_after_transform(reporter);
test_tricky_cubic(reporter);
+ test_arb_round_rect_is_convex(reporter);
+ test_arb_zero_rad_round_rect_is_rect(reporter);
+ test_addrect_isfinite(reporter);
}
#include "TestClassDef.h"
diff --git a/tests/PictureTest.cpp b/tests/PictureTest.cpp
index 44cf59a889..d14cf98737 100644
--- a/tests/PictureTest.cpp
+++ b/tests/PictureTest.cpp
@@ -6,11 +6,231 @@
*/
#include "Test.h"
#include "SkCanvas.h"
+#include "SkColorPriv.h"
+#include "SkData.h"
#include "SkPaint.h"
#include "SkPicture.h"
#include "SkRandom.h"
+#include "SkShader.h"
#include "SkStream.h"
+#include "SkPictureUtils.h"
+
+static void make_bm(SkBitmap* bm, int w, int h, SkColor color, bool immutable) {
+ bm->setConfig(SkBitmap::kARGB_8888_Config, w, h);
+ bm->allocPixels();
+ bm->eraseColor(color);
+ if (immutable) {
+ bm->setImmutable();
+ }
+}
+
+typedef void (*DrawBitmapProc)(SkCanvas*, const SkBitmap&, const SkPoint&);
+
+static void drawbitmap_proc(SkCanvas* canvas, const SkBitmap& bm,
+ const SkPoint& pos) {
+ canvas->drawBitmap(bm, pos.fX, pos.fY, NULL);
+}
+
+static void drawbitmaprect_proc(SkCanvas* canvas, const SkBitmap& bm,
+ const SkPoint& pos) {
+ SkRect r = {
+ 0, 0, SkIntToScalar(bm.width()), SkIntToScalar(bm.height())
+ };
+ r.offset(pos.fX, pos.fY);
+ canvas->drawBitmapRectToRect(bm, NULL, r, NULL);
+}
+
+static void drawshader_proc(SkCanvas* canvas, const SkBitmap& bm,
+ const SkPoint& pos) {
+ SkRect r = {
+ 0, 0, SkIntToScalar(bm.width()), SkIntToScalar(bm.height())
+ };
+ r.offset(pos.fX, pos.fY);
+
+ SkShader* s = SkShader::CreateBitmapShader(bm,
+ SkShader::kClamp_TileMode,
+ SkShader::kClamp_TileMode);
+ SkPaint paint;
+ paint.setShader(s)->unref();
+ canvas->drawRect(r, paint);
+}
+
+// Return a picture with the bitmaps drawn at the specified positions.
+static SkPicture* record_bitmaps(const SkBitmap bm[], const SkPoint pos[],
+ int count, DrawBitmapProc proc) {
+ SkPicture* pic = new SkPicture;
+ SkCanvas* canvas = pic->beginRecording(1000, 1000);
+ for (int i = 0; i < count; ++i) {
+ proc(canvas, bm[i], pos[i]);
+ }
+ pic->endRecording();
+ return pic;
+}
+
+static void rand_rect(SkRect* rect, SkRandom& rand, SkScalar W, SkScalar H) {
+ rect->fLeft = rand.nextRangeScalar(-W, 2*W);
+ rect->fTop = rand.nextRangeScalar(-H, 2*H);
+ rect->fRight = rect->fLeft + rand.nextRangeScalar(0, W);
+ rect->fBottom = rect->fTop + rand.nextRangeScalar(0, H);
+
+ // we integralize rect to make our tests more predictable, since Gather is
+ // a little sloppy.
+ SkIRect ir;
+ rect->round(&ir);
+ rect->set(ir);
+}
+
+// Allocate result to be large enough to hold subset, and then draw the picture
+// into it, offsetting by subset's top/left corner.
+static void draw(SkPicture* pic, const SkRect& subset, SkBitmap* result) {
+ SkIRect ir;
+ subset.roundOut(&ir);
+ int w = ir.width();
+ int h = ir.height();
+ make_bm(result, w, h, 0, false);
+
+ SkCanvas canvas(*result);
+ canvas.translate(-SkIntToScalar(ir.left()), -SkIntToScalar(ir.top()));
+ canvas.drawPicture(*pic);
+}
+
+template <typename T> int find_index(const T* array, T elem, int count) {
+ for (int i = 0; i < count; ++i) {
+ if (array[i] == elem) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+// Return true if 'ref' is found in array[]
+static bool find(SkPixelRef const * const * array, SkPixelRef const * ref, int count) {
+ return find_index<const SkPixelRef*>(array, ref, count) >= 0;
+}
+
+// Look at each pixel in bm, and if its color appears in colors[], find the
+// corresponding value in refs[] and append that ref into array, skipping
+// duplicates of the same value.
+static void gather_from_colors(const SkBitmap& bm, SkPixelRef* const refs[],
+ int count, SkTDArray<SkPixelRef*>* array) {
+ // Since we only want to return unique values in array, when we scan we just
+ // set a bit for each index'd color found. In practice we only have a few
+ // distinct colors, so we just use an int's bits as our array. Hence the
+ // assert that count <= number-of-bits-in-our-int.
+ SkASSERT((unsigned)count <= 32);
+ uint32_t bitarray = 0;
+
+ SkAutoLockPixels alp(bm);
+
+ for (int y = 0; y < bm.height(); ++y) {
+ for (int x = 0; x < bm.width(); ++x) {
+ SkPMColor pmc = *bm.getAddr32(x, y);
+ // the only good case where the color is not found would be if
+ // the color is transparent, meaning no bitmap was drawn in that
+ // pixel.
+ if (pmc) {
+ int index = SkGetPackedR32(pmc);
+ SkASSERT(SkGetPackedG32(pmc) == index);
+ SkASSERT(SkGetPackedB32(pmc) == index);
+ SkASSERT(index < count);
+ bitarray |= 1 << index;
+ }
+ }
+ }
+
+ for (int i = 0; i < count; ++i) {
+ if (bitarray & (1 << i)) {
+ *array->append() = refs[i];
+ }
+ }
+}
+
+static void test_gatherpixelrefs(skiatest::Reporter* reporter) {
+ const int IW = 8;
+ const int IH = IW;
+ const SkScalar W = SkIntToScalar(IW);
+ const SkScalar H = W;
+
+ static const int N = 4;
+ SkBitmap bm[N];
+ SkPixelRef* refs[N];
+
+ const SkPoint pos[] = {
+ { 0, 0 }, { W, 0 }, { 0, H }, { W, H }
+ };
+
+ // Our convention is that the color components contain the index of their
+ // corresponding bitmap/pixelref
+ for (int i = 0; i < N; ++i) {
+ make_bm(&bm[i], IW, IH, SkColorSetARGB(0xFF, i, i, i), true);
+ refs[i] = bm[i].pixelRef();
+ }
+
+ static const DrawBitmapProc procs[] = {
+ drawbitmap_proc, drawbitmaprect_proc, drawshader_proc
+ };
+
+ SkRandom rand;
+ for (size_t k = 0; k < SK_ARRAY_COUNT(procs); ++k) {
+ SkAutoTUnref<SkPicture> pic(record_bitmaps(bm, pos, N, procs[k]));
+
+ // quick check for a small piece of each quadrant, which should just
+ // contain 1 bitmap.
+ for (size_t i = 0; i < SK_ARRAY_COUNT(pos); ++i) {
+ SkRect r;
+ r.set(2, 2, W - 2, H - 2);
+ r.offset(pos[i].fX, pos[i].fY);
+ SkAutoDataUnref data(SkPictureUtils::GatherPixelRefs(pic, r));
+ REPORTER_ASSERT(reporter, data);
+ int count = data->size() / sizeof(SkPixelRef*);
+ REPORTER_ASSERT(reporter, 1 == count);
+ REPORTER_ASSERT(reporter, *(SkPixelRef**)data->data() == refs[i]);
+ }
+
+ // Test a bunch of random (mostly) rects, and compare the gather results
+ // with a deduced list of refs by looking at the colors drawn.
+ for (int j = 0; j < 100; ++j) {
+ SkRect r;
+ rand_rect(&r, rand, 2*W, 2*H);
+
+ SkBitmap result;
+ draw(pic, r, &result);
+ SkTDArray<SkPixelRef*> array;
+
+ SkData* data = SkPictureUtils::GatherPixelRefs(pic, r);
+ size_t dataSize = data ? data->size() : 0;
+ int gatherCount = dataSize / sizeof(SkPixelRef*);
+ SkASSERT(gatherCount * sizeof(SkPixelRef*) == dataSize);
+ SkPixelRef** gatherRefs = data ? (SkPixelRef**)(data->data()) : NULL;
+ SkAutoDataUnref adu(data);
+
+ gather_from_colors(result, refs, N, &array);
+
+ /*
+ * GatherPixelRefs is conservative, so it can return more bitmaps
+ * that we actually can see (usually because of conservative bounds
+ * inflation for antialiasing). Thus our check here is only that
+ * Gather didn't miss any that we actually saw. Even that isn't
+ * a strict requirement on Gather, which is meant to be quick and
+ * only mostly-correct, but at the moment this test should work.
+ */
+ for (int i = 0; i < array.count(); ++i) {
+ bool found = find(gatherRefs, array[i], gatherCount);
+ REPORTER_ASSERT(reporter, found);
+#if 0
+ // enable this block of code to debug failures, as it will rerun
+ // the case that failed.
+ if (!found) {
+ SkData* data = SkPictureUtils::GatherPixelRefs(pic, r);
+ size_t dataSize = data ? data->size() : 0;
+ }
+#endif
+ }
+ }
+ }
+}
+
#ifdef SK_DEBUG
// Ensure that deleting SkPicturePlayback does not assert. Asserts only fire in debug mode, so only
// run in debug mode.
@@ -85,12 +305,101 @@ static void test_peephole(skiatest::Reporter* reporter) {
}
}
+#ifndef SK_DEBUG
+// Only test this is in release mode. We deliberately crash in debug mode, since a valid caller
+// should never do this.
+static void test_bad_bitmap() {
+ // This bitmap has a width and height but no pixels. As a result, attempting to record it will
+ // fail.
+ SkBitmap bm;
+ bm.setConfig(SkBitmap::kARGB_8888_Config, 100, 100);
+ SkPicture picture;
+ SkCanvas* recordingCanvas = picture.beginRecording(100, 100);
+ recordingCanvas->drawBitmap(bm, 0, 0);
+ picture.endRecording();
+
+ SkCanvas canvas;
+ canvas.drawPicture(picture);
+}
+#endif
+
+#include "SkData.h"
+#include "SkImageRef_GlobalPool.h"
+// Class to test SkPixelRef::onRefEncodedData, since there are currently no implementations in skia.
+class SkDataImageRef : public SkImageRef_GlobalPool {
+
+public:
+ SkDataImageRef(SkMemoryStream* stream)
+ : SkImageRef_GlobalPool(stream, SkBitmap::kNo_Config) {
+ SkASSERT(stream != NULL);
+ fData = stream->copyToData();
+ this->setImmutable();
+ }
+
+ ~SkDataImageRef() {
+ fData->unref();
+ }
+
+ virtual SkData* onRefEncodedData() SK_OVERRIDE {
+ fData->ref();
+ return fData;
+ }
+
+private:
+ SkData* fData;
+};
+
+#include "SkImageEncoder.h"
+
+static bool PNGEncodeBitmapToStream(SkWStream* wStream, const SkBitmap& bm) {
+ return SkImageEncoder::EncodeStream(wStream, bm, SkImageEncoder::kPNG_Type, 100);
+}
+
+static SkData* serialized_picture_from_bitmap(const SkBitmap& bitmap) {
+ SkPicture picture;
+ SkCanvas* canvas = picture.beginRecording(bitmap.width(), bitmap.height());
+ canvas->drawBitmap(bitmap, 0, 0);
+ SkDynamicMemoryWStream wStream;
+ picture.serialize(&wStream, &PNGEncodeBitmapToStream);
+ return wStream.copyToData();
+}
+
+static void test_bitmap_with_encoded_data(skiatest::Reporter* reporter) {
+ // Create a bitmap that will be encoded.
+ SkBitmap original;
+ make_bm(&original, 100, 100, SK_ColorBLUE, true);
+ SkDynamicMemoryWStream wStream;
+ if (!SkImageEncoder::EncodeStream(&wStream, original, SkImageEncoder::kPNG_Type, 100)) {
+ return;
+ }
+ SkAutoDataUnref data(wStream.copyToData());
+ SkMemoryStream memStream;
+ memStream.setData(data);
+
+ // Use the encoded bitmap as the data for an image ref.
+ SkBitmap bm;
+ SkAutoTUnref<SkDataImageRef> imageRef(SkNEW_ARGS(SkDataImageRef, (&memStream)));
+ imageRef->getInfo(&bm);
+ bm.setPixelRef(imageRef);
+
+ // Write both bitmaps to pictures, and ensure that the resulting data streams are the same.
+ // Flattening original will follow the old path of performing an encode, while flattening bm
+ // will use the already encoded data.
+ SkAutoDataUnref picture1(serialized_picture_from_bitmap(original));
+ SkAutoDataUnref picture2(serialized_picture_from_bitmap(bm));
+ REPORTER_ASSERT(reporter, picture1->equals(picture2));
+}
+
static void TestPicture(skiatest::Reporter* reporter) {
#ifdef SK_DEBUG
test_deleting_empty_playback();
test_serializing_empty_picture();
+#else
+ test_bad_bitmap();
#endif
test_peephole(reporter);
+ test_gatherpixelrefs(reporter);
+ test_bitmap_with_encoded_data(reporter);
}
#include "TestClassDef.h"
diff --git a/tests/PipeTest.cpp b/tests/PipeTest.cpp
index 7790c1695d..acf288ae8d 100644
--- a/tests/PipeTest.cpp
+++ b/tests/PipeTest.cpp
@@ -32,7 +32,7 @@ static void testDrawingAfterEndRecording(SkCanvas* canvas) {
SkBitmap bm;
bm.setConfig(SkBitmap::kARGB_8888_Config, 2, 2);
bm.allocPixels();
- bm.eraseColor(0);
+ bm.eraseColor(SK_ColorTRANSPARENT);
SkShader* shader = SkShader::CreateBitmapShader(bm, SkShader::kClamp_TileMode,
SkShader::kClamp_TileMode);
diff --git a/tests/QuickRejectTest.cpp b/tests/QuickRejectTest.cpp
index b8fb989966..00008330eb 100644
--- a/tests/QuickRejectTest.cpp
+++ b/tests/QuickRejectTest.cpp
@@ -41,7 +41,7 @@ static void test_drawBitmap(skiatest::Reporter* reporter) {
SkBitmap dst;
dst.setConfig(SkBitmap::kARGB_8888_Config, 10, 10);
dst.allocPixels();
- dst.eraseColor(0);
+ dst.eraseColor(SK_ColorTRANSPARENT);
SkCanvas canvas(dst);
SkPaint paint;
@@ -54,7 +54,7 @@ static void test_drawBitmap(skiatest::Reporter* reporter) {
REPORTER_ASSERT(reporter, 0xFFFFFFFF == *dst.getAddr32(5, 5));
// reverify we are clear again
- dst.eraseColor(0);
+ dst.eraseColor(SK_ColorTRANSPARENT);
REPORTER_ASSERT(reporter, 0 == *dst.getAddr32(5, 5));
// if the bitmap is clipped out, we don't draw it
diff --git a/tests/RegionTest.cpp b/tests/RegionTest.cpp
index 8e66502f5e..5d3946ebfe 100644
--- a/tests/RegionTest.cpp
+++ b/tests/RegionTest.cpp
@@ -9,6 +9,73 @@
#include "SkRegion.h"
#include "SkRandom.h"
+static void Union(SkRegion* rgn, const SkIRect& rect) {
+ rgn->op(rect, SkRegion::kUnion_Op);
+}
+
+#define TEST_NO_INTERSECT(rgn, rect) REPORTER_ASSERT(reporter, !rgn.intersects(rect))
+#define TEST_INTERSECT(rgn, rect) REPORTER_ASSERT(reporter, rgn.intersects(rect))
+#define TEST_NO_CONTAINS(rgn, rect) REPORTER_ASSERT(reporter, !rgn.contains(rect))
+
+// inspired by http://code.google.com/p/skia/issues/detail?id=958
+//
+static void test_fromchrome(skiatest::Reporter* reporter) {
+ SkRegion r;
+ Union(&r, SkIRect::MakeXYWH(0, 0, 1, 1));
+ TEST_NO_INTERSECT(r, SkIRect::MakeXYWH(0, 0, 0, 0));
+ TEST_INTERSECT(r, SkIRect::MakeXYWH(0, 0, 2, 2));
+ TEST_INTERSECT(r, SkIRect::MakeXYWH(-1, 0, 2, 2));
+ TEST_INTERSECT(r, SkIRect::MakeXYWH(-1, -1, 2, 2));
+ TEST_INTERSECT(r, SkIRect::MakeXYWH(0, -1, 2, 2));
+ TEST_INTERSECT(r, SkIRect::MakeXYWH(-1, -1, 3, 3));
+
+ Union(&r, SkIRect::MakeXYWH(0, 0, 3, 3));
+ Union(&r, SkIRect::MakeXYWH(10, 0, 3, 3));
+ Union(&r, SkIRect::MakeXYWH(0, 10, 13, 3));
+ TEST_INTERSECT(r, SkIRect::MakeXYWH(-1, -1, 2, 2));
+ TEST_INTERSECT(r, SkIRect::MakeXYWH(2, -1, 2, 2));
+ TEST_INTERSECT(r, SkIRect::MakeXYWH(2, 2, 2, 2));
+ TEST_INTERSECT(r, SkIRect::MakeXYWH(-1, 2, 2, 2));
+
+ TEST_INTERSECT(r, SkIRect::MakeXYWH(9, -1, 2, 2));
+ TEST_INTERSECT(r, SkIRect::MakeXYWH(12, -1, 2, 2));
+ TEST_INTERSECT(r, SkIRect::MakeXYWH(12, 2, 2, 2));
+ TEST_INTERSECT(r, SkIRect::MakeXYWH(9, 2, 2, 2));
+
+ TEST_INTERSECT(r, SkIRect::MakeXYWH(0, -1, 13, 5));
+ TEST_INTERSECT(r, SkIRect::MakeXYWH(1, -1, 11, 5));
+ TEST_INTERSECT(r, SkIRect::MakeXYWH(2, -1, 9, 5));
+ TEST_INTERSECT(r, SkIRect::MakeXYWH(2, -1, 8, 5));
+ TEST_INTERSECT(r, SkIRect::MakeXYWH(3, -1, 8, 5));
+
+ TEST_INTERSECT(r, SkIRect::MakeXYWH(0, 1, 13, 1));
+ TEST_INTERSECT(r, SkIRect::MakeXYWH(1, 1, 11, 1));
+ TEST_INTERSECT(r, SkIRect::MakeXYWH(2, 1, 9, 1));
+ TEST_INTERSECT(r, SkIRect::MakeXYWH(2, 1, 8, 1));
+ TEST_INTERSECT(r, SkIRect::MakeXYWH(3, 1, 8, 1));
+
+ TEST_INTERSECT(r, SkIRect::MakeXYWH(0, 0, 13, 13));
+ TEST_INTERSECT(r, SkIRect::MakeXYWH(0, 1, 13, 11));
+ TEST_INTERSECT(r, SkIRect::MakeXYWH(0, 2, 13, 9));
+ TEST_INTERSECT(r, SkIRect::MakeXYWH(0, 2, 13, 8));
+
+
+ // These test SkRegion::contains(Rect) and SkRegion::contains(Region)
+
+ SkRegion container;
+ Union(&container, SkIRect::MakeXYWH(0, 0, 40, 20));
+ Union(&container, SkIRect::MakeXYWH(30, 20, 10, 20));
+ TEST_NO_CONTAINS(container, SkIRect::MakeXYWH(0, 0, 10, 39));
+ TEST_NO_CONTAINS(container, SkIRect::MakeXYWH(29, 0, 10, 39));
+
+ {
+ SkRegion rgn;
+ Union(&rgn, SkIRect::MakeXYWH(0, 0, 10, 10));
+ Union(&rgn, SkIRect::MakeLTRB(5, 10, 20, 20));
+ TEST_INTERSECT(rgn, SkIRect::MakeXYWH(15, 0, 5, 11));
+ }
+}
+
static void test_empties(skiatest::Reporter* reporter) {
SkRegion valid(SkIRect::MakeWH(10, 10));
SkRegion empty, empty2;
@@ -185,6 +252,7 @@ static void TestRegion(skiatest::Reporter* reporter) {
test_proc(reporter, contains_proc);
test_proc(reporter, intersects_proc);
test_empties(reporter);
+ test_fromchrome(reporter);
}
#include "TestClassDef.h"
diff --git a/tests/RoundRectTest.cpp b/tests/RoundRectTest.cpp
new file mode 100644
index 0000000000..e88ed8ad8b
--- /dev/null
+++ b/tests/RoundRectTest.cpp
@@ -0,0 +1,308 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "Test.h"
+#include "SkRRect.h"
+
+static const SkScalar kWidth = 100.0f;
+static const SkScalar kHeight = 100.0f;
+
+// Test out the basic API entry points
+static void test_round_rect_basic(skiatest::Reporter* reporter) {
+ // Test out initialization methods
+ SkPoint zeroPt = { 0.0, 0.0 };
+ SkRRect empty;
+
+ empty.setEmpty();
+
+ REPORTER_ASSERT(reporter, SkRRect::kEmpty_Type == empty.type());
+ REPORTER_ASSERT(reporter, empty.rect().isEmpty());
+
+ for (int i = 0; i < 4; ++i) {
+ REPORTER_ASSERT(reporter, zeroPt == empty.radii((SkRRect::Corner) i));
+ }
+
+ //----
+ SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
+
+ SkRRect rr1;
+ rr1.setRect(rect);
+
+ REPORTER_ASSERT(reporter, SkRRect::kRect_Type == rr1.type());
+ REPORTER_ASSERT(reporter, rr1.rect() == rect);
+
+ for (int i = 0; i < 4; ++i) {
+ REPORTER_ASSERT(reporter, zeroPt == rr1.radii((SkRRect::Corner) i));
+ }
+
+ //----
+ SkPoint halfPoint = { SkScalarHalf(kWidth), SkScalarHalf(kHeight) };
+ SkRRect rr2;
+ rr2.setOval(rect);
+
+ REPORTER_ASSERT(reporter, SkRRect::kOval_Type == rr2.type());
+ REPORTER_ASSERT(reporter, rr2.rect() == rect);
+
+ for (int i = 0; i < 4; ++i) {
+ REPORTER_ASSERT(reporter,
+ rr2.radii((SkRRect::Corner) i).equalsWithinTolerance(halfPoint));
+ }
+
+ //----
+ SkPoint p = { 5, 5 };
+ SkRRect rr3;
+ rr3.setRectXY(rect, p.fX, p.fY);
+
+ REPORTER_ASSERT(reporter, SkRRect::kSimple_Type == rr3.type());
+ REPORTER_ASSERT(reporter, rr3.rect() == rect);
+
+ for (int i = 0; i < 4; ++i) {
+ REPORTER_ASSERT(reporter, p == rr3.radii((SkRRect::Corner) i));
+ }
+
+ //----
+ SkPoint radii[4] = { { 5, 5 }, { 5, 5 }, { 5, 5 }, { 5, 5 } };
+
+ SkRRect rr4;
+ rr4.setRectRadii(rect, radii);
+
+ REPORTER_ASSERT(reporter, SkRRect::kSimple_Type == rr4.type());
+ REPORTER_ASSERT(reporter, rr4.rect() == rect);
+
+ for (int i = 0; i < 4; ++i) {
+ REPORTER_ASSERT(reporter, radii[i] == rr4.radii((SkRRect::Corner) i));
+ }
+
+ //----
+ SkPoint radii2[4] = { { 0, 0 }, { 0, 0 }, { 50, 50 }, { 20, 50 } };
+
+ SkRRect rr5;
+ rr5.setRectRadii(rect, radii2);
+
+ REPORTER_ASSERT(reporter, SkRRect::kComplex_Type == rr5.type());
+ REPORTER_ASSERT(reporter, rr5.rect() == rect);
+
+ for (int i = 0; i < 4; ++i) {
+ REPORTER_ASSERT(reporter, radii2[i] == rr5.radii((SkRRect::Corner) i));
+ }
+
+ // Test out == & !=
+ REPORTER_ASSERT(reporter, empty != rr3);
+ REPORTER_ASSERT(reporter, rr3 == rr4);
+ REPORTER_ASSERT(reporter, rr4 != rr5);
+}
+
+// Test out the cases when the RR degenerates to a rect
+static void test_round_rect_rects(skiatest::Reporter* reporter) {
+ SkRect r;
+ static const SkPoint pts[] = {
+ // Upper Left
+ { -SK_Scalar1, -SK_Scalar1 }, // out
+ { SK_Scalar1, SK_Scalar1 }, // in
+ // Upper Right
+ { SkIntToScalar(101), -SK_Scalar1}, // out
+ { SkIntToScalar(99), SK_Scalar1 }, // in
+ // Lower Right
+ { SkIntToScalar(101), SkIntToScalar(101) }, // out
+ { SkIntToScalar(99), SkIntToScalar(99) }, // in
+ // Lower Left
+ { -SK_Scalar1, SkIntToScalar(101) }, // out
+ { SK_Scalar1, SkIntToScalar(99) }, // in
+ // Middle
+ { SkIntToScalar(50), SkIntToScalar(50) } // in
+ };
+ static const bool isIn[] = { false, true, false, true, false, true, false, true, true };
+
+ SkASSERT(SK_ARRAY_COUNT(pts) == SK_ARRAY_COUNT(isIn));
+
+ //----
+ SkRRect empty;
+
+ empty.setEmpty();
+
+ REPORTER_ASSERT(reporter, SkRRect::kEmpty_Type == empty.type());
+ r = empty.rect();
+ REPORTER_ASSERT(reporter, 0 == r.fLeft && 0 == r.fTop && 0 == r.fRight && 0 == r.fBottom);
+
+ //----
+ SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
+ SkRRect rr1;
+ rr1.setRectXY(rect, 0, 0);
+
+ REPORTER_ASSERT(reporter, SkRRect::kRect_Type == rr1.type());
+ r = rr1.rect();
+ REPORTER_ASSERT(reporter, rect == r);
+ for (size_t i = 0; i < SK_ARRAY_COUNT(pts); ++i) {
+ REPORTER_ASSERT(reporter, isIn[i] == rr1.contains(pts[i].fX, pts[i].fY));
+ }
+
+ //----
+ SkPoint radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } };
+
+ SkRRect rr2;
+ rr2.setRectRadii(rect, radii);
+
+ REPORTER_ASSERT(reporter, SkRRect::kRect_Type == rr2.type());
+ r = rr2.rect();
+ REPORTER_ASSERT(reporter, rect == r);
+ for (size_t i = 0; i < SK_ARRAY_COUNT(pts); ++i) {
+ REPORTER_ASSERT(reporter, isIn[i] == rr2.contains(pts[i].fX, pts[i].fY));
+ }
+
+ //----
+ SkPoint radii2[4] = { { 0, 0 }, { 20, 20 }, { 50, 50 }, { 20, 50 } };
+
+ SkRRect rr3;
+ rr3.setRectRadii(rect, radii2);
+ REPORTER_ASSERT(reporter, SkRRect::kComplex_Type == rr3.type());
+}
+
+// Test out the cases when the RR degenerates to an oval
+static void test_round_rect_ovals(skiatest::Reporter* reporter) {
+ static const SkScalar kEps = 0.1f;
+ static const SkScalar kWidthTol = SkScalarHalf(kWidth) * (SK_Scalar1 - SK_ScalarRoot2Over2);
+ static const SkScalar kHeightTol = SkScalarHalf(kHeight) * (SK_Scalar1 - SK_ScalarRoot2Over2);
+ static const SkPoint pts[] = {
+ // Upper Left
+ { kWidthTol - kEps, kHeightTol - kEps }, // out
+ { kWidthTol + kEps, kHeightTol + kEps }, // in
+ // Upper Right
+ { kWidth + kEps - kWidthTol, kHeightTol - kEps }, // out
+ { kWidth - kEps - kWidthTol, kHeightTol + kEps }, // in
+ // Lower Right
+ { kWidth + kEps - kWidthTol, kHeight + kEps - kHeightTol }, // out
+ { kWidth - kEps - kWidthTol, kHeight - kEps - kHeightTol }, // in
+ // Lower Left
+ { kWidthTol - kEps, kHeight + kEps - kHeightTol }, //out
+ { kWidthTol + kEps, kHeight - kEps - kHeightTol }, // in
+ // Middle
+ { SkIntToScalar(50), SkIntToScalar(50) } // in
+ };
+ static const bool isIn[] = { false, true, false, true, false, true, false, true, true };
+
+ SkASSERT(SK_ARRAY_COUNT(pts) == SK_ARRAY_COUNT(isIn));
+
+ //----
+ SkRect oval;
+ SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
+ SkRRect rr1;
+ rr1.setRectXY(rect, SkScalarHalf(kWidth), SkScalarHalf(kHeight));
+
+ REPORTER_ASSERT(reporter, SkRRect::kOval_Type == rr1.type());
+ oval = rr1.rect();
+ REPORTER_ASSERT(reporter, oval == rect);
+ for (size_t i = 0; i < SK_ARRAY_COUNT(pts); ++i) {
+ REPORTER_ASSERT(reporter, isIn[i] == rr1.contains(pts[i].fX, pts[i].fY));
+ }
+}
+
+// Test out the non-degenerate RR cases
+static void test_round_rect_general(skiatest::Reporter* reporter) {
+ static const SkScalar kEps = 0.1f;
+ static const SkScalar kDist20 = 20 * (SK_Scalar1 - SK_ScalarRoot2Over2);
+ static const SkPoint pts[] = {
+ // Upper Left
+ { kDist20 - kEps, kDist20 - kEps }, // out
+ { kDist20 + kEps, kDist20 + kEps }, // in
+ // Upper Right
+ { kWidth + kEps - kDist20, kDist20 - kEps }, // out
+ { kWidth - kEps - kDist20, kDist20 + kEps }, // in
+ // Lower Right
+ { kWidth + kEps - kDist20, kHeight + kEps - kDist20 }, // out
+ { kWidth - kEps - kDist20, kHeight - kEps - kDist20 }, // in
+ // Lower Left
+ { kDist20 - kEps, kHeight + kEps - kDist20 }, //out
+ { kDist20 + kEps, kHeight - kEps - kDist20 }, // in
+ // Middle
+ { SkIntToScalar(50), SkIntToScalar(50) } // in
+ };
+ static const bool isIn[] = { false, true, false, true, false, true, false, true, true };
+
+ SkASSERT(SK_ARRAY_COUNT(pts) == SK_ARRAY_COUNT(isIn));
+
+ //----
+ SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
+ SkRRect rr1;
+ rr1.setRectXY(rect, 20, 20);
+
+ REPORTER_ASSERT(reporter, SkRRect::kSimple_Type == rr1.type());
+ for (size_t i = 0; i < SK_ARRAY_COUNT(pts); ++i) {
+ REPORTER_ASSERT(reporter, isIn[i] == rr1.contains(pts[i].fX, pts[i].fY));
+ }
+
+ //----
+ static const SkScalar kDist50 = 50*(SK_Scalar1 - SK_ScalarRoot2Over2);
+ static const SkPoint pts2[] = {
+ // Upper Left
+ { -SK_Scalar1, -SK_Scalar1 }, // out
+ { SK_Scalar1, SK_Scalar1 }, // in
+ // Upper Right
+ { kWidth + kEps - kDist20, kDist20 - kEps }, // out
+ { kWidth - kEps - kDist20, kDist20 + kEps }, // in
+ // Lower Right
+ { kWidth + kEps - kDist50, kHeight + kEps - kDist50 }, // out
+ { kWidth - kEps - kDist50, kHeight - kEps - kDist50 }, // in
+ // Lower Left
+ { kDist20 - kEps, kHeight + kEps - kDist50 }, // out
+ { kDist20 + kEps, kHeight - kEps - kDist50 }, // in
+ // Middle
+ { SkIntToScalar(50), SkIntToScalar(50) } // in
+ };
+
+ SkASSERT(SK_ARRAY_COUNT(pts2) == SK_ARRAY_COUNT(isIn));
+
+ SkPoint radii[4] = { { 0, 0 }, { 20, 20 }, { 50, 50 }, { 20, 50 } };
+
+ SkRRect rr2;
+ rr2.setRectRadii(rect, radii);
+
+ REPORTER_ASSERT(reporter, SkRRect::kComplex_Type == rr2.type());
+ for (size_t i = 0; i < SK_ARRAY_COUNT(pts); ++i) {
+ REPORTER_ASSERT(reporter, isIn[i] == rr2.contains(pts2[i].fX, pts2[i].fY));
+ }
+}
+
+// Test out questionable-parameter handling
+static void test_round_rect_iffy_parameters(skiatest::Reporter* reporter) {
+
+ // When the radii exceed the base rect they are proportionally scaled down
+ // to fit
+ SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
+ SkPoint radii[4] = { { 50, 100 }, { 100, 50 }, { 50, 100 }, { 100, 50 } };
+
+ SkRRect rr1;
+ rr1.setRectRadii(rect, radii);
+
+ REPORTER_ASSERT(reporter, SkRRect::kComplex_Type == rr1.type());
+
+ const SkPoint& p = rr1.radii(SkRRect::kUpperLeft_Corner);
+
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(p.fX, 33.33333f));
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(p.fY, 66.66666f));
+
+ // Negative radii should be capped at zero
+ SkRRect rr2;
+ rr2.setRectXY(rect, -10, -20);
+
+ REPORTER_ASSERT(reporter, SkRRect::kRect_Type == rr2.type());
+
+ const SkPoint& p2 = rr2.radii(SkRRect::kUpperLeft_Corner);
+
+ REPORTER_ASSERT(reporter, 0.0f == p2.fX);
+ REPORTER_ASSERT(reporter, 0.0f == p2.fY);
+}
+
+static void TestRoundRect(skiatest::Reporter* reporter) {
+ test_round_rect_basic(reporter);
+ test_round_rect_rects(reporter);
+ test_round_rect_ovals(reporter);
+ test_round_rect_general(reporter);
+ test_round_rect_iffy_parameters(reporter);
+}
+
+#include "TestClassDef.h"
+DEFINE_TESTCLASS("RoundRect", TestRoundRectClass, TestRoundRect)
diff --git a/tests/SortTest.cpp b/tests/SortTest.cpp
index 65c48639a2..4783ba989f 100644
--- a/tests/SortTest.cpp
+++ b/tests/SortTest.cpp
@@ -7,34 +7,8 @@
*/
#include "Test.h"
#include "SkRandom.h"
-#include "SkChecksum.h"
#include "SkTSort.h"
-// assert that as we change values (from 0 to non-zero) in our buffer, we
-// get a different value
-static void test_checksum(skiatest::Reporter* reporter, size_t size) {
- SkAutoMalloc storage(size);
- uint32_t* ptr = (uint32_t*)storage.get();
- char* cptr = (char*)ptr;
-
- sk_bzero(ptr, size);
- uint32_t prev = 0;
- for (size_t i = 0; i < size; ++i) {
- cptr[i] = 0x5B; // just need some non-zero value here
- uint32_t curr = SkChecksum::Compute(ptr, size);
- REPORTER_ASSERT(reporter, prev != curr);
- prev = curr;
- }
-}
-
-static void test_checksum(skiatest::Reporter* reporter) {
- REPORTER_ASSERT(reporter, SkChecksum::Compute(NULL, 0) == 0);
-
- for (size_t size = 4; size <= 1000; size += 4) {
- test_checksum(reporter, size);
- }
-}
-
extern "C" {
static int compare_int(const void* a, const void* b) {
return *(const int*)a - *(const int*)b;
@@ -77,8 +51,6 @@ static void TestSort(skiatest::Reporter* reporter) {
if (false) { // avoid bit rot, suppress warning
compare_int(array, array);
}
-
- test_checksum(reporter);
}
// need tests for SkStrSearch
diff --git a/tests/StringTest.cpp b/tests/StringTest.cpp
index 5ae718fed3..2c5026c682 100644
--- a/tests/StringTest.cpp
+++ b/tests/StringTest.cpp
@@ -57,10 +57,14 @@ static void TestString(skiatest::Reporter* reporter) {
REPORTER_ASSERT(reporter, !a.equals("help"));
REPORTER_ASSERT(reporter, a.startsWith("hell"));
+ REPORTER_ASSERT(reporter, a.startsWith('h'));
REPORTER_ASSERT(reporter, !a.startsWith( "ell"));
+ REPORTER_ASSERT(reporter, !a.startsWith( 'e'));
REPORTER_ASSERT(reporter, a.startsWith(""));
REPORTER_ASSERT(reporter, a.endsWith("llo"));
+ REPORTER_ASSERT(reporter, a.endsWith('o'));
REPORTER_ASSERT(reporter, !a.endsWith("ll" ));
+ REPORTER_ASSERT(reporter, !a.endsWith('l'));
REPORTER_ASSERT(reporter, a.endsWith(""));
REPORTER_ASSERT(reporter, a.contains("he"));
REPORTER_ASSERT(reporter, a.contains("ll"));
@@ -68,6 +72,8 @@ static void TestString(skiatest::Reporter* reporter) {
REPORTER_ASSERT(reporter, a.contains("hello"));
REPORTER_ASSERT(reporter, !a.contains("hellohello"));
REPORTER_ASSERT(reporter, a.contains(""));
+ REPORTER_ASSERT(reporter, a.contains('e'));
+ REPORTER_ASSERT(reporter, !a.contains('z'));
SkString e(a);
SkString f("hello");
diff --git a/tests/StrokeTest.cpp b/tests/StrokeTest.cpp
new file mode 100644
index 0000000000..ded1130be4
--- /dev/null
+++ b/tests/StrokeTest.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "Test.h"
+#include "SkPaint.h"
+#include "SkPath.h"
+#include "SkRect.h"
+#include "SkStroke.h"
+
+static bool equal(const SkRect& a, const SkRect& b) {
+ return SkScalarNearlyEqual(a.left(), b.left()) &&
+ SkScalarNearlyEqual(a.top(), b.top()) &&
+ SkScalarNearlyEqual(a.right(), b.right()) &&
+ SkScalarNearlyEqual(a.bottom(), b.bottom());
+}
+
+static void test_strokerect(skiatest::Reporter* reporter) {
+ const SkScalar width = SkIntToScalar(10);
+ SkPaint paint;
+
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setStrokeWidth(width);
+
+ SkRect r = { 0, 0, SkIntToScalar(200), SkIntToScalar(100) };
+
+ SkRect outer(r);
+ outer.outset(width/2, width/2);
+
+ static const SkPaint::Join joins[] = {
+ SkPaint::kMiter_Join, SkPaint::kRound_Join, SkPaint::kBevel_Join
+ };
+
+ for (size_t i = 0; i < SK_ARRAY_COUNT(joins); ++i) {
+ paint.setStrokeJoin(joins[i]);
+
+ SkPath path, fillPath;
+ path.addRect(r);
+ paint.getFillPath(path, &fillPath);
+
+ REPORTER_ASSERT(reporter, equal(outer, fillPath.getBounds()));
+
+ bool isMiter = SkPaint::kMiter_Join == joins[i];
+ SkRect nested[2];
+ REPORTER_ASSERT(reporter, fillPath.isNestedRects(nested) == isMiter);
+ if (isMiter) {
+ SkRect inner(r);
+ inner.inset(width/2, width/2);
+ REPORTER_ASSERT(reporter, equal(nested[0], outer));
+ REPORTER_ASSERT(reporter, equal(nested[1], inner));
+ }
+ }
+}
+
+static void TestStroke(skiatest::Reporter* reporter) {
+ test_strokerect(reporter);
+}
+
+#include "TestClassDef.h"
+DEFINE_TESTCLASS("Stroke", TestStrokeClass, TestStroke)
+
diff --git a/tests/TDLinkedListTest.cpp b/tests/TDLinkedListTest.cpp
deleted file mode 100644
index 8df39b8927..0000000000
--- a/tests/TDLinkedListTest.cpp
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2012 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "Test.h"
-#include "SkTDLinkedList.h"
-
-class ListElement {
-public:
- ListElement(int id) : fID(id) {
- }
-
- int fID;
-
-private:
- SK_DEFINE_DLINKEDLIST_INTERFACE(ListElement);
-};
-
-static void CheckList(const SkTDLinkedList<ListElement>& list,
- skiatest::Reporter* reporter,
- bool empty,
- int numElements,
- bool in0, bool in1, bool in2, bool in3,
- ListElement elements[4]) {
-
- REPORTER_ASSERT(reporter, empty == list.isEmpty());
-#if SK_DEBUG
- REPORTER_ASSERT(reporter, numElements == list.countEntries());
- REPORTER_ASSERT(reporter, in0 == list.isInList(&elements[0]));
- REPORTER_ASSERT(reporter, in1 == list.isInList(&elements[1]));
- REPORTER_ASSERT(reporter, in2 == list.isInList(&elements[2]));
- REPORTER_ASSERT(reporter, in3 == list.isInList(&elements[3]));
-#endif
-}
-
-static void TestTDLinkedList(skiatest::Reporter* reporter) {
- SkTDLinkedList<ListElement> list;
- ListElement elements[4] = {
- ListElement(0),
- ListElement(1),
- ListElement(2),
- ListElement(3),
- };
-
- // list should be empty to start with
- CheckList(list, reporter, true, 0, false, false, false, false, elements);
-
- list.addToHead(&elements[0]);
-
- CheckList(list, reporter, false, 1, true, false, false, false, elements);
-
- list.addToHead(&elements[1]);
- list.addToHead(&elements[2]);
- list.addToHead(&elements[3]);
-
- CheckList(list, reporter, false, 4, true, true, true, true, elements);
-
- // test out iterators
- SkTDLinkedList<ListElement>::Iter iter;
-
- ListElement* cur = iter.init(list, SkTDLinkedList<ListElement>::Iter::kHead_IterStart);
- for (int i = 0; NULL != cur; ++i, cur = iter.next()) {
- REPORTER_ASSERT(reporter, cur->fID == 3-i);
- }
-
- cur = iter.init(list, SkTDLinkedList<ListElement>::Iter::kTail_IterStart);
- for (int i = 0; NULL != cur; ++i, cur = iter.prev()) {
- REPORTER_ASSERT(reporter, cur->fID == i);
- }
-
- // remove middle, frontmost then backmost
- list.remove(&elements[1]);
- list.remove(&elements[3]);
- list.remove(&elements[0]);
-
- CheckList(list, reporter, false, 1, false, false, true, false, elements);
-
- // remove last element
- list.remove(&elements[2]);
-
- // list should be empty again
- CheckList(list, reporter, true, 0, false, false, false, false, elements);
-}
-
-#include "TestClassDef.h"
-DEFINE_TESTCLASS("TDLinkedList", TestTDLinkedListClass, TestTDLinkedList)
diff --git a/tests/TLSTest.cpp b/tests/TLSTest.cpp
index 5fa0903ca8..17f7dcba25 100644
--- a/tests/TLSTest.cpp
+++ b/tests/TLSTest.cpp
@@ -30,13 +30,13 @@ static void thread_main(void*) {
}
}
-static void test_measuretext(skiatest::Reporter* reporter) {
+static void test_threads(SkThread::entryPointProc proc) {
SkThread* threads[8];
int N = SK_ARRAY_COUNT(threads);
int i;
for (i = 0; i < N; ++i) {
- threads[i] = new SkThread(thread_main);
+ threads[i] = new SkThread(proc);
}
for (i = 0; i < N; ++i) {
@@ -52,12 +52,32 @@ static void test_measuretext(skiatest::Reporter* reporter) {
}
}
+static int32_t gCounter;
+
+static void* FakeCreateTLS() {
+ sk_atomic_inc(&gCounter);
+ return NULL;
+}
+
+static void FakeDeleteTLS(void* unused) {
+ sk_atomic_dec(&gCounter);
+}
+
+static void testTLSDestructor(void* unused) {
+ SkTLS::Get(FakeCreateTLS, FakeDeleteTLS);
+}
+
static void TestTLS(skiatest::Reporter* reporter) {
- test_measuretext(reporter);
+ // TODO: Disabled for now to work around
+ // http://code.google.com/p/skia/issues/detail?id=619
+ // ('flaky segfault in TLS test on Shuttle_Ubuntu12 buildbots')
+ //test_threads(&thread_main);
+
+ // Test to ensure that at thread destruction, TLS destructors
+ // have been called.
+ test_threads(&testTLSDestructor);
+ REPORTER_ASSERT(reporter, 0 == gCounter);
}
#include "TestClassDef.h"
-// TODO: Disabled for now to work around
-// http://code.google.com/p/skia/issues/detail?id=619
-// ('flaky segfault in TLS test on Shuttle_Ubuntu12 buildbots')
-// DEFINE_TESTCLASS("TLS", TLSClass, TestTLS)
+DEFINE_TESTCLASS("TLS", TLSClass, TestTLS)
diff --git a/tests/Test.cpp b/tests/Test.cpp
index deb83483c2..6953d9435d 100644
--- a/tests/Test.cpp
+++ b/tests/Test.cpp
@@ -83,18 +83,27 @@ bool Test::run() {
///////////////////////////////////////////////////////////////////////////////
-
-GrContext* GpuTest::GetContext() {
#if SK_SUPPORT_GPU
- // preserve this order, we want gGrContext destroyed after gEGLContext
- static SkTLazy<SkNativeGLContext> gGLContext;
+ static SkAutoTUnref<SkNativeGLContext> gGLContext;
static SkAutoTUnref<GrContext> gGrContext;
+#endif
+
+void GpuTest::DestroyContext() {
+#if SK_SUPPORT_GPU
+ // preserve this order, we want gGrContext destroyed before gGLContext
+ gGrContext.reset(NULL);
+ gGLContext.reset(NULL);
+#endif
+}
+
+GrContext* GpuTest::GetContext() {
+#if SK_SUPPORT_GPU
if (NULL == gGrContext.get()) {
- gGLContext.init();
+ gGLContext.reset(new SkNativeGLContext());
if (gGLContext.get()->init(800, 600)) {
- GrPlatform3DContext ctx = reinterpret_cast<GrPlatform3DContext>(gGLContext.get()->gl());
- gGrContext.reset(GrContext::Create(kOpenGL_Shaders_GrEngine, ctx));
+ GrBackendContext ctx = reinterpret_cast<GrBackendContext>(gGLContext.get()->gl());
+ gGrContext.reset(GrContext::Create(kOpenGL_GrBackend, ctx));
}
}
if (gGLContext.get()) {
diff --git a/tests/Test.h b/tests/Test.h
index f87a7a07b2..2cbd00b594 100644
--- a/tests/Test.h
+++ b/tests/Test.h
@@ -104,6 +104,7 @@ namespace skiatest {
fContext = GetContext();
}
static GrContext* GetContext();
+ static void DestroyContext();
protected:
GrContext* fContext;
private:
diff --git a/tests/TileGridTest.cpp b/tests/TileGridTest.cpp
new file mode 100644
index 0000000000..1946b9f39e
--- /dev/null
+++ b/tests/TileGridTest.cpp
@@ -0,0 +1,135 @@
+
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "Test.h"
+#include "SkTileGrid.h"
+#include "SkTileGridPicture.h"
+#include "SkCanvas.h"
+#include "SkDevice.h"
+
+enum Tile {
+ kTopLeft_Tile = 0x1,
+ kTopRight_Tile = 0x2,
+ kBottomLeft_Tile = 0x4,
+ kBottomRight_Tile = 0x8,
+
+ kAll_Tile = kTopLeft_Tile | kTopRight_Tile | kBottomLeft_Tile | kBottomRight_Tile,
+};
+
+namespace {
+class MockCanvas : public SkCanvas {
+public:
+ MockCanvas(SkDevice* device) : SkCanvas(device)
+ {}
+
+ virtual void drawRect(const SkRect& rect, const SkPaint& paint)
+ {
+ // This capture occurs before quick reject.
+ fRects.push(rect);
+ }
+
+ SkTDArray<SkRect> fRects;
+};
+}
+
+class TileGridTest {
+public:
+ static void verifyTileHits(skiatest::Reporter* reporter, SkIRect rect, uint32_t tileMask) {
+ SkTileGrid grid(10, 10, 2, 2, NULL);
+ grid.insert(NULL, rect, false);
+ REPORTER_ASSERT(reporter, grid.tile(0,0).count() ==
+ ((tileMask & kTopLeft_Tile)? 1 : 0));
+ REPORTER_ASSERT(reporter, grid.tile(1,0).count() ==
+ ((tileMask & kTopRight_Tile)? 1 : 0));
+ REPORTER_ASSERT(reporter, grid.tile(0,1).count() ==
+ ((tileMask & kBottomLeft_Tile)? 1 : 0));
+ REPORTER_ASSERT(reporter, grid.tile(1,1).count() ==
+ ((tileMask & kBottomRight_Tile)? 1 : 0));
+ }
+
+ static void TestUnalignedQuery(skiatest::Reporter* reporter) {
+ // Use SkTileGridPicture to generate a SkTileGrid with a helper
+ SkTileGridPicture picture(10, 10, 20, 20);
+ SkRect rect1 = SkRect::MakeXYWH(SkIntToScalar(0), SkIntToScalar(0),
+ SkIntToScalar(8), SkIntToScalar(8));
+ SkRect rect2 = SkRect::MakeXYWH(SkIntToScalar(11), SkIntToScalar(11),
+ SkIntToScalar(1), SkIntToScalar(1));
+ SkCanvas* canvas = picture.beginRecording(20, 20, SkPicture::kOptimizeForClippedPlayback_RecordingFlag);
+ SkPaint paint;
+ canvas->drawRect(rect1, paint);
+ canvas->drawRect(rect2, paint);
+ picture.endRecording();
+
+ SkBitmap store;
+ store.setConfig(SkBitmap::kARGB_8888_Config, 1, 1);
+ store.allocPixels();
+
+ // Test parts of top-left tile
+ {
+ SkDevice device(store);
+ MockCanvas mockCanvas(&device);
+ picture.draw(&mockCanvas);
+ REPORTER_ASSERT(reporter, 1 == mockCanvas.fRects.count());
+ REPORTER_ASSERT(reporter, rect1 == mockCanvas.fRects[0]);
+ }
+ {
+ SkDevice device(store);
+ MockCanvas mockCanvas(&device);
+ mockCanvas.translate(SkFloatToScalar(-7.99f), SkFloatToScalar(-7.99f));
+ picture.draw(&mockCanvas);
+ REPORTER_ASSERT(reporter, 1 == mockCanvas.fRects.count());
+ REPORTER_ASSERT(reporter, rect1 == mockCanvas.fRects[0]);
+ }
+ // Corner overlap
+ {
+ SkDevice device(store);
+ MockCanvas mockCanvas(&device);
+ mockCanvas.translate(SkFloatToScalar(-9.5f), SkFloatToScalar(-9.5f));
+ picture.draw(&mockCanvas);
+ REPORTER_ASSERT(reporter, 2 == mockCanvas.fRects.count());
+ REPORTER_ASSERT(reporter, rect1 == mockCanvas.fRects[0]);
+ REPORTER_ASSERT(reporter, rect2 == mockCanvas.fRects[1]);
+ }
+ // Intersect bottom right tile, but does not overlap rect 2
+ {
+ SkDevice device(store);
+ MockCanvas mockCanvas(&device);
+ mockCanvas.translate(SkFloatToScalar(-16.0f), SkFloatToScalar(-16.0f));
+ picture.draw(&mockCanvas);
+ REPORTER_ASSERT(reporter, 1 == mockCanvas.fRects.count());
+ REPORTER_ASSERT(reporter, rect2 == mockCanvas.fRects[0]);
+ }
+ }
+
+ static void Test(skiatest::Reporter* reporter) {
+ // Out of bounds
+ verifyTileHits(reporter, SkIRect::MakeXYWH(30, 0, 1, 1), 0);
+ verifyTileHits(reporter, SkIRect::MakeXYWH(0, 30, 1, 1), 0);
+ verifyTileHits(reporter, SkIRect::MakeXYWH(-10, 0, 1, 1), 0);
+ verifyTileHits(reporter, SkIRect::MakeXYWH(0, -10, 1, 1), 0);
+
+ // Dilation for AA consideration
+ verifyTileHits(reporter, SkIRect::MakeXYWH(0, 0, 8, 8), kTopLeft_Tile);
+ verifyTileHits(reporter, SkIRect::MakeXYWH(0, 0, 9, 9), kAll_Tile);
+ verifyTileHits(reporter, SkIRect::MakeXYWH(10, 10, 1, 1), kAll_Tile);
+ verifyTileHits(reporter, SkIRect::MakeXYWH(11, 11, 1, 1), kBottomRight_Tile);
+
+ // BBoxes that overlap tiles
+ verifyTileHits(reporter, SkIRect::MakeXYWH(5, 5, 10, 1), kTopLeft_Tile | kTopRight_Tile);
+ verifyTileHits(reporter, SkIRect::MakeXYWH(5, 5, 1, 10), kTopLeft_Tile |
+ kBottomLeft_Tile);
+ verifyTileHits(reporter, SkIRect::MakeXYWH(5, 5, 10, 10), kAll_Tile);
+ verifyTileHits(reporter, SkIRect::MakeXYWH(-10, -10, 40, 40), kAll_Tile);
+
+ TestUnalignedQuery(reporter);
+ }
+};
+
+
+#include "TestClassDef.h"
+DEFINE_TESTCLASS("TileGrid", TileGridTestClass, TileGridTest::Test)
diff --git a/tests/skia_test.cpp b/tests/skia_test.cpp
index f884a6458b..5e944f7774 100644
--- a/tests/skia_test.cpp
+++ b/tests/skia_test.cpp
@@ -60,7 +60,7 @@ static const char* result2string(Reporter::Result result) {
class DebugfReporter : public Reporter {
public:
- DebugfReporter(bool androidMode) : fAndroidMode(androidMode) {}
+ DebugfReporter() : fIndex(0), fTotal(0) {}
void setIndexOfTotal(int index, int total) {
fIndex = index;
@@ -68,42 +68,18 @@ public:
}
protected:
virtual void onStart(Test* test) {
- this->dumpState(test, kStarting_State);
+ SkDebugf("[%d/%d] %s...\n", fIndex+1, fTotal, test->getName());
}
virtual void onReport(const char desc[], Reporter::Result result) {
- if (!fAndroidMode) {
- SkDebugf("\t%s: %s\n", result2string(result), desc);
- }
+ SkDebugf("\t%s: %s\n", result2string(result), desc);
}
virtual void onEnd(Test* test) {
- this->dumpState(test, this->getCurrSuccess() ?
- kSucceeded_State : kFailed_State);
- }
-private:
- enum State {
- kStarting_State = 1,
- kSucceeded_State = 0,
- kFailed_State = -2
- };
-
- void dumpState(Test* test, State state) {
- if (fAndroidMode) {
- SkDebugf("INSTRUMENTATION_STATUS: test=%s\n", test->getName());
- SkDebugf("INSTRUMENTATION_STATUS: class=com.skia\n");
- SkDebugf("INSTRUMENTATION_STATUS: current=%d\n", fIndex+1);
- SkDebugf("INSTRUMENTATION_STATUS: numtests=%d\n", fTotal);
- SkDebugf("INSTRUMENTATION_STATUS_CODE: %d\n", state);
- } else {
- if (kStarting_State == state) {
- SkDebugf("[%d/%d] %s...\n", fIndex+1, fTotal, test->getName());
- } else if (kFailed_State == state) {
- SkDebugf("---- FAILED\n");
- }
+ if (!this->getCurrSuccess()) {
+ SkDebugf("---- FAILED\n");
}
}
-
+private:
int fIndex, fTotal;
- bool fAndroidMode;
};
int tool_main(int argc, char** argv);
@@ -113,15 +89,11 @@ int tool_main(int argc, char** argv) {
#endif
SkGraphics::Init();
- bool androidMode = false;
const char* matchStr = NULL;
char* const* stop = argv + argc;
for (++argv; argv < stop; ++argv) {
- if (strcmp(*argv, "-android") == 0) {
- androidMode = true;
-
- } else if (strcmp(*argv, "--match") == 0) {
+ if (strcmp(*argv, "--match") == 0) {
++argv;
if (argv < stop && **argv) {
matchStr = *argv;
@@ -144,12 +116,10 @@ int tool_main(int argc, char** argv) {
#else
header.append(" SK_SCALAR_IS_FLOAT");
#endif
- if (!androidMode) {
- SkDebugf("%s\n", header.c_str());
- }
+ SkDebugf("%s\n", header.c_str());
}
- DebugfReporter reporter(androidMode);
+ DebugfReporter reporter;
Iter iter(&reporter);
Test* test;
@@ -170,10 +140,8 @@ int tool_main(int argc, char** argv) {
index += 1;
}
- if (!androidMode) {
- SkDebugf("Finished %d tests, %d failures, %d skipped.\n",
- count, failCount, skipCount);
- }
+ SkDebugf("Finished %d tests, %d failures, %d skipped.\n",
+ count, failCount, skipCount);
#if SK_SUPPORT_GPU
@@ -186,11 +154,12 @@ int tool_main(int argc, char** argv) {
#endif
SkGraphics::Term();
+ GpuTest::DestroyContext();
return (failCount == 0) ? 0 : 1;
}
-#if !defined SK_BUILD_FOR_IOS
+#if !defined(SK_BUILD_FOR_IOS) && !defined(SK_BUILD_FOR_NACL)
int main(int argc, char * const argv[]) {
return tool_main(argc, (char**) argv);
}
diff --git a/tools/CopyTilesRenderer.cpp b/tools/CopyTilesRenderer.cpp
new file mode 100644
index 0000000000..d3f266b028
--- /dev/null
+++ b/tools/CopyTilesRenderer.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "picture_utils.h"
+#include "CopyTilesRenderer.h"
+#include "SkCanvas.h"
+#include "SkDevice.h"
+#include "SkImageEncoder.h"
+#include "SkPicture.h"
+#include "SkPixelRef.h"
+#include "SkRect.h"
+#include "SkString.h"
+
+namespace sk_tools {
+ CopyTilesRenderer::CopyTilesRenderer(int x, int y)
+ : fXTilesPerLargeTile(x)
+ , fYTilesPerLargeTile(y) {
+ }
+ void CopyTilesRenderer::init(SkPicture* pict) {
+ SkASSERT(pict != NULL);
+ // Only work with absolute widths (as opposed to percentages).
+ SkASSERT(this->getTileWidth() != 0 && this->getTileHeight() != 0);
+ fPicture = pict;
+ fPicture->ref();
+ this->buildBBoxHierarchy();
+ // In order to avoid allocating a large canvas (particularly important for GPU), create one
+ // canvas that is a multiple of the tile size, and draw portions of the picture.
+ fLargeTileWidth = fXTilesPerLargeTile * this->getTileWidth();
+ fLargeTileHeight = fYTilesPerLargeTile * this->getTileHeight();
+ fCanvas.reset(this->INHERITED::setupCanvas(fLargeTileWidth, fLargeTileHeight));
+ }
+
+ bool CopyTilesRenderer::render(const SkString* path, SkBitmap** out) {
+ int i = 0;
+ bool success = true;
+ SkBitmap dst;
+ for (int x = 0; x < this->getViewWidth(); x += fLargeTileWidth) {
+ for (int y = 0; y < this->getViewHeight(); y += fLargeTileHeight) {
+ SkAutoCanvasRestore autoRestore(fCanvas, true);
+ // Translate so that we draw the correct portion of the picture.
+ // Perform a postTranslate so that the scaleFactor does not interfere with the
+ // positioning.
+ SkMatrix mat(fCanvas->getTotalMatrix());
+ mat.postTranslate(SkIntToScalar(-x), SkIntToScalar(-y));
+ fCanvas->setMatrix(mat);
+ // Draw the picture
+ fCanvas->drawPicture(*fPicture);
+ // Now extract the picture into tiles
+ const SkBitmap& baseBitmap = fCanvas->getDevice()->accessBitmap(false);
+ SkIRect subset;
+ for (int tileY = 0; tileY < fLargeTileHeight; tileY += this->getTileHeight()) {
+ for (int tileX = 0; tileX < fLargeTileWidth; tileX += this->getTileWidth()) {
+ subset.set(tileX, tileY, tileX + this->getTileWidth(),
+ tileY + this->getTileHeight());
+ SkDEBUGCODE(bool extracted =)
+ baseBitmap.extractSubset(&dst, subset);
+ SkASSERT(extracted);
+ if (path != NULL) {
+ // Similar to writeAppendNumber in PictureRenderer.cpp, but just encodes
+ // a bitmap directly.
+ SkString pathWithNumber(*path);
+ pathWithNumber.appendf("%i.png", i++);
+ SkBitmap copy;
+#if SK_SUPPORT_GPU
+ if (isUsingGpuDevice()) {
+ dst.pixelRef()->readPixels(&copy, &subset);
+ } else {
+#endif
+ dst.copyTo(&copy, dst.config());
+#if SK_SUPPORT_GPU
+ }
+#endif
+ success &= SkImageEncoder::EncodeFile(pathWithNumber.c_str(), copy,
+ SkImageEncoder::kPNG_Type, 100);
+ }
+ }
+ }
+ }
+ }
+ return success;
+ }
+
+ SkString CopyTilesRenderer::getConfigNameInternal() {
+ return SkString("copy_tiles");
+ }
+}
diff --git a/tools/CopyTilesRenderer.h b/tools/CopyTilesRenderer.h
new file mode 100644
index 0000000000..5546604ee8
--- /dev/null
+++ b/tools/CopyTilesRenderer.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef CopyTilesRenderer_DEFINED
+#define CopyTilesRenderer_DEFINED
+
+#include "PictureRenderer.h"
+#include "SkTypes.h"
+
+class SkPicture;
+class SkString;
+
+namespace sk_tools {
+ /**
+ * PictureRenderer that draws the picture and then extracts it into tiles. For large pictures,
+ * it will divide the picture into large tiles and draw the picture once for each large tile.
+ */
+ class CopyTilesRenderer : public TiledPictureRenderer {
+
+ public:
+ CopyTilesRenderer(int x, int y);
+ virtual void init(SkPicture* pict) SK_OVERRIDE;
+
+ /**
+ * Similar to TiledPictureRenderer, this will draw a PNG for each tile. However, the
+ * numbering (and actual tiles) will be different.
+ */
+ virtual bool render(const SkString* path, SkBitmap** out) SK_OVERRIDE;
+
+ private:
+ int fXTilesPerLargeTile;
+ int fYTilesPerLargeTile;
+
+ int fLargeTileWidth;
+ int fLargeTileHeight;
+
+ virtual SkString getConfigNameInternal() SK_OVERRIDE;
+
+ typedef TiledPictureRenderer INHERITED;
+ };
+} // sk_tools
+#endif // CopyTilesRenderer_DEFINED
diff --git a/tools/PictureBenchmark.cpp b/tools/PictureBenchmark.cpp
new file mode 100644
index 0000000000..bdf1306101
--- /dev/null
+++ b/tools/PictureBenchmark.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBenchLogger.h"
+#include "BenchTimer.h"
+#include "PictureBenchmark.h"
+#include "SkCanvas.h"
+#include "SkPicture.h"
+#include "SkString.h"
+#include "picture_utils.h"
+#include "TimerData.h"
+
+namespace sk_tools {
+
+PictureBenchmark::PictureBenchmark()
+: fRepeats(1)
+, fLogger(NULL)
+, fRenderer(NULL)
+, fLogPerIter(false)
+, fPrintMin(false)
+, fShowWallTime(false)
+, fShowTruncatedWallTime(false)
+, fShowCpuTime(true)
+, fShowTruncatedCpuTime(false)
+, fShowGpuTime(false)
+, fTimeIndividualTiles(false)
+{}
+
+PictureBenchmark::~PictureBenchmark() {
+ SkSafeUnref(fRenderer);
+}
+
+BenchTimer* PictureBenchmark::setupTimer() {
+#if SK_SUPPORT_GPU
+ if (fRenderer != NULL && fRenderer->isUsingGpuDevice()) {
+ return SkNEW_ARGS(BenchTimer, (fRenderer->getGLContext()));
+ }
+#endif
+ return SkNEW_ARGS(BenchTimer, (NULL));
+}
+
+void PictureBenchmark::logProgress(const char msg[]) {
+ if (fLogger != NULL) {
+ fLogger->logProgress(msg);
+ }
+}
+
+PictureRenderer* PictureBenchmark::setRenderer(sk_tools::PictureRenderer* renderer) {
+ SkRefCnt_SafeAssign(fRenderer, renderer);
+ return renderer;
+}
+
+void PictureBenchmark::run(SkPicture* pict) {
+ SkASSERT(pict);
+ if (NULL == pict) {
+ return;
+ }
+
+ SkASSERT(fRenderer != NULL);
+ if (NULL == fRenderer) {
+ return;
+ }
+
+ fRenderer->init(pict);
+
+ // We throw this away to remove first time effects (such as paging in this program)
+ fRenderer->setup();
+ fRenderer->render(NULL);
+ fRenderer->resetState();
+
+ BenchTimer* timer = this->setupTimer();
+ bool usingGpu = false;
+#if SK_SUPPORT_GPU
+ usingGpu = fRenderer->isUsingGpuDevice();
+#endif
+
+ if (fTimeIndividualTiles) {
+ TiledPictureRenderer* tiledRenderer = fRenderer->getTiledRenderer();
+ SkASSERT(tiledRenderer);
+ if (NULL == tiledRenderer) {
+ return;
+ }
+ int xTiles, yTiles;
+ if (!tiledRenderer->tileDimensions(xTiles, yTiles)) {
+ return;
+ }
+
+ // Insert a newline so that each tile is reported on its own line (separate from the line
+ // that describes the skp being run).
+ this->logProgress("\n");
+
+ int x, y;
+ while (tiledRenderer->nextTile(x, y)) {
+ TimerData timerData(tiledRenderer->getPerIterTimeFormat(),
+ tiledRenderer->getNormalTimeFormat());
+ for (int i = 0; i < fRepeats; ++i) {
+ timer->start();
+ tiledRenderer->drawCurrentTile();
+ timer->truncatedEnd();
+ tiledRenderer->resetState();
+ timer->end();
+ timerData.appendTimes(timer, fRepeats - 1 == i);
+ }
+ SkString configName = tiledRenderer->getConfigName();
+ configName.appendf(": tile [%i,%i] out of [%i,%i]", x, y, xTiles, yTiles);
+ SkString result = timerData.getResult(fLogPerIter, fPrintMin, fRepeats,
+ configName.c_str(), fShowWallTime,
+ fShowTruncatedWallTime, fShowCpuTime,
+ fShowTruncatedCpuTime, usingGpu && fShowGpuTime);
+ result.append("\n");
+ this->logProgress(result.c_str());
+ }
+ } else {
+ TimerData timerData(fRenderer->getPerIterTimeFormat(), fRenderer->getNormalTimeFormat());
+ for (int i = 0; i < fRepeats; ++i) {
+ fRenderer->setup();
+
+ timer->start();
+ fRenderer->render(NULL);
+ timer->truncatedEnd();
+
+ // Finishes gl context
+ fRenderer->resetState();
+ timer->end();
+
+ timerData.appendTimes(timer, fRepeats - 1 == i);
+ }
+
+ SkString configName = fRenderer->getConfigName();
+ SkString result = timerData.getResult(fLogPerIter, fPrintMin, fRepeats,
+ configName.c_str(), fShowWallTime,
+ fShowTruncatedWallTime, fShowCpuTime,
+ fShowTruncatedCpuTime, usingGpu && fShowGpuTime);
+ result.append("\n");
+ this->logProgress(result.c_str());
+ }
+
+ fRenderer->end();
+ SkDELETE(timer);
+}
+
+}
diff --git a/tools/PictureBenchmark.h b/tools/PictureBenchmark.h
new file mode 100644
index 0000000000..af81f69717
--- /dev/null
+++ b/tools/PictureBenchmark.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef PictureBenchmark_DEFINED
+#define PictureBenchmark_DEFINED
+
+#include "SkTypes.h"
+#include "PictureRenderer.h"
+
+class BenchTimer;
+class SkBenchLogger;
+class SkPicture;
+class SkString;
+
+namespace sk_tools {
+
+class PictureBenchmark {
+public:
+ PictureBenchmark();
+
+ ~PictureBenchmark();
+
+ /**
+ * Draw the provided SkPicture fRepeats times while collecting timing data, and log the output
+ * via fLogger.
+ */
+ void run(SkPicture* pict);
+
+ void setRepeats(int repeats) {
+ fRepeats = repeats;
+ }
+
+ /**
+ * If true, tells run to log separate timing data for each individual tile. Each tile will be
+ * drawn fRepeats times. Requires the PictureRenderer set by setRenderer to be a
+ * TiledPictureRenderer.
+ */
+ void setTimeIndividualTiles(bool indiv) { fTimeIndividualTiles = true; }
+
+ bool timeIndividualTiles() { return fTimeIndividualTiles; }
+
+ PictureRenderer* setRenderer(PictureRenderer*);
+
+ void setDeviceType(PictureRenderer::SkDeviceTypes deviceType) {
+ if (fRenderer != NULL) {
+ fRenderer->setDeviceType(deviceType);
+ }
+ }
+
+ void setLogPerIter(bool log) { fLogPerIter = log; }
+
+ void setPrintMin(bool min) { fPrintMin = min; }
+
+ void setTimersToShow(bool wall, bool truncatedWall, bool cpu, bool truncatedCpu, bool gpu) {
+ fShowWallTime = wall;
+ fShowTruncatedWallTime = truncatedWall;
+ fShowCpuTime = cpu;
+ fShowTruncatedCpuTime = truncatedCpu;
+ fShowGpuTime = gpu;
+ }
+
+ void setLogger(SkBenchLogger* logger) { fLogger = logger; }
+
+private:
+ int fRepeats;
+ SkBenchLogger* fLogger;
+ PictureRenderer* fRenderer;
+ bool fLogPerIter;
+ bool fPrintMin;
+ bool fShowWallTime;
+ bool fShowTruncatedWallTime;
+ bool fShowCpuTime;
+ bool fShowTruncatedCpuTime;
+ bool fShowGpuTime;
+ bool fTimeIndividualTiles;
+
+ void logProgress(const char msg[]);
+
+ BenchTimer* setupTimer();
+};
+
+}
+
+#endif // PictureBenchmark_DEFINED
diff --git a/tools/PictureRenderer.cpp b/tools/PictureRenderer.cpp
new file mode 100644
index 0000000000..61de8c63b0
--- /dev/null
+++ b/tools/PictureRenderer.cpp
@@ -0,0 +1,833 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "PictureRenderer.h"
+#include "picture_utils.h"
+#include "SamplePipeControllers.h"
+#include "SkCanvas.h"
+#include "SkDevice.h"
+#include "SkGPipe.h"
+#if SK_SUPPORT_GPU
+#include "SkGpuDevice.h"
+#endif
+#include "SkGraphics.h"
+#include "SkImageEncoder.h"
+#include "SkMaskFilter.h"
+#include "SkMatrix.h"
+#include "SkPicture.h"
+#include "SkRTree.h"
+#include "SkScalar.h"
+#include "SkStream.h"
+#include "SkString.h"
+#include "SkTemplates.h"
+#include "SkTileGridPicture.h"
+#include "SkTDArray.h"
+#include "SkThreadUtils.h"
+#include "SkTypes.h"
+#include "SkData.h"
+#include "SkPictureUtils.h"
+
+namespace sk_tools {
+
+enum {
+ kDefaultTileWidth = 256,
+ kDefaultTileHeight = 256
+};
+
+void PictureRenderer::init(SkPicture* pict) {
+ SkASSERT(NULL == fPicture);
+ SkASSERT(NULL == fCanvas.get());
+ if (fPicture != NULL || NULL != fCanvas.get()) {
+ return;
+ }
+
+ SkASSERT(pict != NULL);
+ if (NULL == pict) {
+ return;
+ }
+
+ fPicture = pict;
+ fPicture->ref();
+ fCanvas.reset(this->setupCanvas());
+}
+
+class FlagsDrawFilter : public SkDrawFilter {
+public:
+ FlagsDrawFilter(PictureRenderer::DrawFilterFlags* flags) :
+ fFlags(flags) {}
+
+ virtual bool filter(SkPaint* paint, Type t) {
+ paint->setFlags(paint->getFlags() & ~fFlags[t] & SkPaint::kAllFlags);
+ if (PictureRenderer::kBlur_DrawFilterFlag & fFlags[t]) {
+ SkMaskFilter* maskFilter = paint->getMaskFilter();
+ SkMaskFilter::BlurInfo blurInfo;
+ if (maskFilter && maskFilter->asABlur(&blurInfo)) {
+ paint->setMaskFilter(NULL);
+ }
+ }
+ if (PictureRenderer::kHinting_DrawFilterFlag & fFlags[t]) {
+ paint->setHinting(SkPaint::kNo_Hinting);
+ } else if (PictureRenderer::kSlightHinting_DrawFilterFlag & fFlags[t]) {
+ paint->setHinting(SkPaint::kSlight_Hinting);
+ }
+ return true;
+ }
+
+private:
+ PictureRenderer::DrawFilterFlags* fFlags;
+};
+
+static void setUpFilter(SkCanvas* canvas, PictureRenderer::DrawFilterFlags* drawFilters) {
+ if (drawFilters && !canvas->getDrawFilter()) {
+ canvas->setDrawFilter(SkNEW_ARGS(FlagsDrawFilter, (drawFilters)))->unref();
+ if (drawFilters[0] & PictureRenderer::kAAClip_DrawFilterFlag) {
+ canvas->setAllowSoftClip(false);
+ }
+ }
+}
+
+SkCanvas* PictureRenderer::setupCanvas() {
+ const int width = this->getViewWidth();
+ const int height = this->getViewHeight();
+ return this->setupCanvas(width, height);
+}
+
+SkCanvas* PictureRenderer::setupCanvas(int width, int height) {
+ SkCanvas* canvas;
+ switch(fDeviceType) {
+ case kBitmap_DeviceType: {
+ SkBitmap bitmap;
+ sk_tools::setup_bitmap(&bitmap, width, height);
+ canvas = SkNEW_ARGS(SkCanvas, (bitmap));
+ }
+ break;
+#if SK_SUPPORT_GPU
+ case kGPU_DeviceType: {
+ SkAutoTUnref<SkGpuDevice> device(SkNEW_ARGS(SkGpuDevice,
+ (fGrContext, SkBitmap::kARGB_8888_Config,
+ width, height)));
+ canvas = SkNEW_ARGS(SkCanvas, (device.get()));
+ }
+ break;
+#endif
+ default:
+ SkASSERT(0);
+ return NULL;
+ }
+ setUpFilter(canvas, fDrawFilters);
+ this->scaleToScaleFactor(canvas);
+ return canvas;
+}
+
+void PictureRenderer::scaleToScaleFactor(SkCanvas* canvas) {
+ SkASSERT(canvas != NULL);
+ if (fScaleFactor != SK_Scalar1) {
+ canvas->scale(fScaleFactor, fScaleFactor);
+ }
+}
+
+void PictureRenderer::end() {
+ this->resetState();
+ SkSafeUnref(fPicture);
+ fPicture = NULL;
+ fCanvas.reset(NULL);
+}
+
+int PictureRenderer::getViewWidth() {
+ SkASSERT(fPicture != NULL);
+ int width = fPicture->width();
+ if (fViewport.width() > 0) {
+ width = SkMin32(width, fViewport.width());
+ }
+ return width;
+}
+
+int PictureRenderer::getViewHeight() {
+ SkASSERT(fPicture != NULL);
+ int height = fPicture->height();
+ if (fViewport.height() > 0) {
+ height = SkMin32(height, fViewport.height());
+ }
+ return height;
+}
+
+/** Converts fPicture to a picture that uses a BBoxHierarchy.
+ * PictureRenderer subclasses that are used to test picture playback
+ * should call this method during init.
+ */
+void PictureRenderer::buildBBoxHierarchy() {
+ SkASSERT(NULL != fPicture);
+ if (kNone_BBoxHierarchyType != fBBoxHierarchyType && NULL != fPicture) {
+ SkPicture* newPicture = this->createPicture();
+ SkCanvas* recorder = newPicture->beginRecording(fPicture->width(), fPicture->height(),
+ this->recordFlags());
+ fPicture->draw(recorder);
+ newPicture->endRecording();
+ fPicture->unref();
+ fPicture = newPicture;
+ }
+}
+
+void PictureRenderer::resetState() {
+#if SK_SUPPORT_GPU
+ if (this->isUsingGpuDevice()) {
+ SkGLContext* glContext = fGrContextFactory.getGLContext(
+ GrContextFactory::kNative_GLContextType);
+
+ SkASSERT(glContext != NULL);
+ if (NULL == glContext) {
+ return;
+ }
+
+ fGrContext->flush();
+ SK_GL(*glContext, Finish());
+ }
+#endif
+}
+
+uint32_t PictureRenderer::recordFlags() {
+ return kNone_BBoxHierarchyType == fBBoxHierarchyType ? 0 :
+ SkPicture::kOptimizeForClippedPlayback_RecordingFlag;
+}
+
+/**
+ * Write the canvas to the specified path.
+ * @param canvas Must be non-null. Canvas to be written to a file.
+ * @param path Path for the file to be written. Should have no extension; write() will append
+ * an appropriate one. Passed in by value so it can be modified.
+ * @return bool True if the Canvas is written to a file.
+ */
+static bool write(SkCanvas* canvas, SkString path) {
+ SkASSERT(canvas != NULL);
+ if (NULL == canvas) {
+ return false;
+ }
+
+ SkBitmap bitmap;
+ SkISize size = canvas->getDeviceSize();
+ sk_tools::setup_bitmap(&bitmap, size.width(), size.height());
+
+ canvas->readPixels(&bitmap, 0, 0);
+ sk_tools::force_all_opaque(bitmap);
+
+ // Since path is passed in by value, it is okay to modify it.
+ path.append(".png");
+ return SkImageEncoder::EncodeFile(path.c_str(), bitmap, SkImageEncoder::kPNG_Type, 100);
+}
+
+/**
+ * If path is non NULL, append number to it, and call write(SkCanvas*, SkString) to write the
+ * provided canvas to a file. Returns true if path is NULL or if write() succeeds.
+ */
+static bool writeAppendNumber(SkCanvas* canvas, const SkString* path, int number) {
+ if (NULL == path) {
+ return true;
+ }
+ SkString pathWithNumber(*path);
+ pathWithNumber.appendf("%i", number);
+ return write(canvas, pathWithNumber);
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+SkCanvas* RecordPictureRenderer::setupCanvas(int width, int height) {
+ // defer the canvas setup until the render step
+ return NULL;
+}
+
+static bool PNGEncodeBitmapToStream(SkWStream* wStream, const SkBitmap& bm) {
+ return SkImageEncoder::EncodeStream(wStream, bm, SkImageEncoder::kPNG_Type, 100);
+}
+
+bool RecordPictureRenderer::render(const SkString* path, SkBitmap** out) {
+ SkAutoTUnref<SkPicture> replayer(this->createPicture());
+ SkCanvas* recorder = replayer->beginRecording(this->getViewWidth(), this->getViewHeight(),
+ this->recordFlags());
+ this->scaleToScaleFactor(recorder);
+ fPicture->draw(recorder);
+ replayer->endRecording();
+ if (path != NULL) {
+ // Record the new picture as a new SKP with PNG encoded bitmaps.
+ SkString skpPath(*path);
+ // ".skp" was removed from 'path' before being passed in here.
+ skpPath.append(".skp");
+ SkFILEWStream stream(skpPath.c_str());
+ replayer->serialize(&stream, &PNGEncodeBitmapToStream);
+ return true;
+ }
+ return false;
+}
+
+SkString RecordPictureRenderer::getConfigNameInternal() {
+ return SkString("record");
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+bool PipePictureRenderer::render(const SkString* path, SkBitmap** out) {
+ SkASSERT(fCanvas.get() != NULL);
+ SkASSERT(fPicture != NULL);
+ if (NULL == fCanvas.get() || NULL == fPicture) {
+ return false;
+ }
+
+ PipeController pipeController(fCanvas.get());
+ SkGPipeWriter writer;
+ SkCanvas* pipeCanvas = writer.startRecording(&pipeController);
+ pipeCanvas->drawPicture(*fPicture);
+ writer.endRecording();
+ fCanvas->flush();
+ if (NULL != path) {
+ return write(fCanvas, *path);
+ }
+ if (NULL != out) {
+ *out = SkNEW(SkBitmap);
+ setup_bitmap(*out, fPicture->width(), fPicture->height());
+ fCanvas->readPixels(*out, 0, 0);
+ }
+ return true;
+}
+
+SkString PipePictureRenderer::getConfigNameInternal() {
+ return SkString("pipe");
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+void SimplePictureRenderer::init(SkPicture* picture) {
+ INHERITED::init(picture);
+ this->buildBBoxHierarchy();
+}
+
+bool SimplePictureRenderer::render(const SkString* path, SkBitmap** out) {
+ SkASSERT(fCanvas.get() != NULL);
+ SkASSERT(fPicture != NULL);
+ if (NULL == fCanvas.get() || NULL == fPicture) {
+ return false;
+ }
+
+ fCanvas->drawPicture(*fPicture);
+ fCanvas->flush();
+ if (NULL != path) {
+ return write(fCanvas, *path);
+ }
+
+ if (NULL != out) {
+ *out = SkNEW(SkBitmap);
+ setup_bitmap(*out, fPicture->width(), fPicture->height());
+ fCanvas->readPixels(*out, 0, 0);
+ }
+
+ return true;
+}
+
+SkString SimplePictureRenderer::getConfigNameInternal() {
+ return SkString("simple");
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+TiledPictureRenderer::TiledPictureRenderer()
+ : fTileWidth(kDefaultTileWidth)
+ , fTileHeight(kDefaultTileHeight)
+ , fTileWidthPercentage(0.0)
+ , fTileHeightPercentage(0.0)
+ , fTileMinPowerOf2Width(0)
+ , fCurrentTileOffset(-1)
+ , fTilesX(0)
+ , fTilesY(0) { }
+
+void TiledPictureRenderer::init(SkPicture* pict) {
+ SkASSERT(pict != NULL);
+ SkASSERT(0 == fTileRects.count());
+ if (NULL == pict || fTileRects.count() != 0) {
+ return;
+ }
+
+ // Do not call INHERITED::init(), which would create a (potentially large) canvas which is not
+ // used by bench_pictures.
+ fPicture = pict;
+ fPicture->ref();
+ this->buildBBoxHierarchy();
+
+ if (fTileWidthPercentage > 0) {
+ fTileWidth = sk_float_ceil2int(float(fTileWidthPercentage * fPicture->width() / 100));
+ }
+ if (fTileHeightPercentage > 0) {
+ fTileHeight = sk_float_ceil2int(float(fTileHeightPercentage * fPicture->height() / 100));
+ }
+
+ if (fTileMinPowerOf2Width > 0) {
+ this->setupPowerOf2Tiles();
+ } else {
+ this->setupTiles();
+ }
+ fCanvas.reset(this->setupCanvas(fTileWidth, fTileHeight));
+ // Initialize to -1 so that the first call to nextTile will set this up to draw tile 0 on the
+ // first call to drawCurrentTile.
+ fCurrentTileOffset = -1;
+}
+
+void TiledPictureRenderer::end() {
+ fTileRects.reset();
+ this->INHERITED::end();
+}
+
+void TiledPictureRenderer::setupTiles() {
+ // Only use enough tiles to cover the viewport
+ const int width = this->getViewWidth();
+ const int height = this->getViewHeight();
+
+ fTilesX = fTilesY = 0;
+ for (int tile_y_start = 0; tile_y_start < height; tile_y_start += fTileHeight) {
+ fTilesY++;
+ for (int tile_x_start = 0; tile_x_start < width; tile_x_start += fTileWidth) {
+ if (0 == tile_y_start) {
+ // Only count tiles in the X direction on the first pass.
+ fTilesX++;
+ }
+ *fTileRects.append() = SkRect::MakeXYWH(SkIntToScalar(tile_x_start),
+ SkIntToScalar(tile_y_start),
+ SkIntToScalar(fTileWidth),
+ SkIntToScalar(fTileHeight));
+ }
+ }
+}
+
+bool TiledPictureRenderer::tileDimensions(int &x, int &y) {
+ if (fTileRects.count() == 0 || NULL == fPicture) {
+ return false;
+ }
+ x = fTilesX;
+ y = fTilesY;
+ return true;
+}
+
+// The goal of the powers of two tiles is to minimize the amount of wasted tile
+// space in the width-wise direction and then minimize the number of tiles. The
+// constraints are that every tile must have a pixel width that is a power of
+// two and also be of some minimal width (that is also a power of two).
+//
+// This is solved by first taking our picture size and rounding it up to the
+// multiple of the minimal width. The binary representation of this rounded
+// value gives us the tiles we need: a bit of value one means we need a tile of
+// that size.
+void TiledPictureRenderer::setupPowerOf2Tiles() {
+ // Only use enough tiles to cover the viewport
+ const int width = this->getViewWidth();
+ const int height = this->getViewHeight();
+
+ int rounded_value = width;
+ if (width % fTileMinPowerOf2Width != 0) {
+ rounded_value = width - (width % fTileMinPowerOf2Width) + fTileMinPowerOf2Width;
+ }
+
+ int num_bits = SkScalarCeilToInt(SkScalarLog2(SkIntToScalar(width)));
+ int largest_possible_tile_size = 1 << num_bits;
+
+ fTilesX = fTilesY = 0;
+ // The tile height is constant for a particular picture.
+ for (int tile_y_start = 0; tile_y_start < height; tile_y_start += fTileHeight) {
+ fTilesY++;
+ int tile_x_start = 0;
+ int current_width = largest_possible_tile_size;
+ // Set fTileWidth to be the width of the widest tile, so that each canvas is large enough
+ // to draw each tile.
+ fTileWidth = current_width;
+
+ while (current_width >= fTileMinPowerOf2Width) {
+ // It is very important this is a bitwise AND.
+ if (current_width & rounded_value) {
+ if (0 == tile_y_start) {
+ // Only count tiles in the X direction on the first pass.
+ fTilesX++;
+ }
+ *fTileRects.append() = SkRect::MakeXYWH(SkIntToScalar(tile_x_start),
+ SkIntToScalar(tile_y_start),
+ SkIntToScalar(current_width),
+ SkIntToScalar(fTileHeight));
+ tile_x_start += current_width;
+ }
+
+ current_width >>= 1;
+ }
+ }
+}
+
+/**
+ * Draw the specified playback to the canvas translated to rectangle provided, so that this mini
+ * canvas represents the rectangle's portion of the overall picture.
+ * Saves and restores so that the initial clip and matrix return to their state before this function
+ * is called.
+ */
+template<class T>
+static void DrawTileToCanvas(SkCanvas* canvas, const SkRect& tileRect, T* playback) {
+ int saveCount = canvas->save();
+ // Translate so that we draw the correct portion of the picture.
+ // Perform a postTranslate so that the scaleFactor does not interfere with the positioning.
+ SkMatrix mat(canvas->getTotalMatrix());
+ mat.postTranslate(-tileRect.fLeft, -tileRect.fTop);
+ canvas->setMatrix(mat);
+ playback->draw(canvas);
+ canvas->restoreToCount(saveCount);
+ canvas->flush();
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+static void bitmapCopySubset(const SkBitmap& src, SkBitmap* dst, int xDst,
+ int yDst) {
+ for (int y = 0; y <src.height() && y + yDst < dst->height() ; y++) {
+ for (int x = 0; x < src.width() && x + xDst < dst->width() ; x++) {
+ *dst->getAddr32(xDst + x, yDst + y) = *src.getAddr32(x, y);
+ }
+ }
+}
+
+bool TiledPictureRenderer::nextTile(int &i, int &j) {
+ if (++fCurrentTileOffset < fTileRects.count()) {
+ i = fCurrentTileOffset % fTilesX;
+ j = fCurrentTileOffset / fTilesX;
+ return true;
+ }
+ return false;
+}
+
+void TiledPictureRenderer::drawCurrentTile() {
+ SkASSERT(fCurrentTileOffset >= 0 && fCurrentTileOffset < fTileRects.count());
+ DrawTileToCanvas(fCanvas, fTileRects[fCurrentTileOffset], fPicture);
+}
+
+bool TiledPictureRenderer::render(const SkString* path, SkBitmap** out) {
+ SkASSERT(fPicture != NULL);
+ if (NULL == fPicture) {
+ return false;
+ }
+
+ SkBitmap bitmap;
+ if (out){
+ *out = SkNEW(SkBitmap);
+ setup_bitmap(*out, fPicture->width(), fPicture->height());
+ setup_bitmap(&bitmap, fTileWidth, fTileHeight);
+ }
+ bool success = true;
+ for (int i = 0; i < fTileRects.count(); ++i) {
+ DrawTileToCanvas(fCanvas, fTileRects[i], fPicture);
+ if (NULL != path) {
+ success &= writeAppendNumber(fCanvas, path, i);
+ }
+ if (NULL != out) {
+ if (fCanvas->readPixels(&bitmap, 0, 0)) {
+ bitmapCopySubset(bitmap, *out, fTileRects[i].left(),
+ fTileRects[i].top());
+ } else {
+ success = false;
+ }
+ }
+ }
+ return success;
+}
+
+SkCanvas* TiledPictureRenderer::setupCanvas(int width, int height) {
+ SkCanvas* canvas = this->INHERITED::setupCanvas(width, height);
+ SkASSERT(fPicture != NULL);
+ // Clip the tile to an area that is completely inside both the SkPicture and the viewport. This
+ // is mostly important for tiles on the right and bottom edges as they may go over this area and
+ // the picture may have some commands that draw outside of this area and so should not actually
+ // be written.
+ // Uses a clipRegion so that it will be unaffected by the scale factor, which may have been set
+ // by INHERITED::setupCanvas.
+ SkRegion clipRegion;
+ clipRegion.setRect(0, 0, this->getViewWidth(), this->getViewHeight());
+ canvas->clipRegion(clipRegion);
+ return canvas;
+}
+
+SkString TiledPictureRenderer::getConfigNameInternal() {
+ SkString name;
+ if (fTileMinPowerOf2Width > 0) {
+ name.append("pow2tile_");
+ name.appendf("%i", fTileMinPowerOf2Width);
+ } else {
+ name.append("tile_");
+ if (fTileWidthPercentage > 0) {
+ name.appendf("%.f%%", fTileWidthPercentage);
+ } else {
+ name.appendf("%i", fTileWidth);
+ }
+ }
+ name.append("x");
+ if (fTileHeightPercentage > 0) {
+ name.appendf("%.f%%", fTileHeightPercentage);
+ } else {
+ name.appendf("%i", fTileHeight);
+ }
+ return name;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+// Holds all of the information needed to draw a set of tiles.
+class CloneData : public SkRunnable {
+
+public:
+ CloneData(SkPicture* clone, SkCanvas* canvas, SkTDArray<SkRect>& rects, int start, int end,
+ SkRunnable* done)
+ : fClone(clone)
+ , fCanvas(canvas)
+ , fPath(NULL)
+ , fRects(rects)
+ , fStart(start)
+ , fEnd(end)
+ , fSuccess(NULL)
+ , fDone(done) {
+ SkASSERT(fDone != NULL);
+ }
+
+ virtual void run() SK_OVERRIDE {
+ SkGraphics::SetTLSFontCacheLimit(1024 * 1024);
+
+ SkBitmap bitmap;
+ if (fBitmap != NULL) {
+ // All tiles are the same size.
+ setup_bitmap(&bitmap, fRects[0].width(), fRects[0].height());
+ }
+
+ for (int i = fStart; i < fEnd; i++) {
+ DrawTileToCanvas(fCanvas, fRects[i], fClone);
+ if (fPath != NULL && !writeAppendNumber(fCanvas, fPath, i)
+ && fSuccess != NULL) {
+ *fSuccess = false;
+ // If one tile fails to write to a file, do not continue drawing the rest.
+ break;
+ }
+ if (fBitmap != NULL) {
+ if (fCanvas->readPixels(&bitmap, 0, 0)) {
+ SkAutoLockPixels alp(*fBitmap);
+ bitmapCopySubset(bitmap, fBitmap, fRects[i].left(),
+ fRects[i].top());
+ } else {
+ *fSuccess = false;
+ // If one tile fails to read pixels, do not continue drawing the rest.
+ break;
+ }
+ }
+ }
+ fDone->run();
+ }
+
+ void setPathAndSuccess(const SkString* path, bool* success) {
+ fPath = path;
+ fSuccess = success;
+ }
+
+ void setBitmap(SkBitmap* bitmap) {
+ fBitmap = bitmap;
+ }
+
+private:
+ // All pointers unowned.
+ SkPicture* fClone; // Picture to draw from. Each CloneData has a unique one which
+ // is threadsafe.
+ SkCanvas* fCanvas; // Canvas to draw to. Reused for each tile.
+ const SkString* fPath; // If non-null, path to write the result to as a PNG.
+ SkTDArray<SkRect>& fRects; // All tiles of the picture.
+ const int fStart; // Range of tiles drawn by this thread.
+ const int fEnd;
+ bool* fSuccess; // Only meaningful if path is non-null. Shared by all threads,
+ // and only set to false upon failure to write to a PNG.
+ SkRunnable* fDone;
+ SkBitmap* fBitmap;
+};
+
+MultiCorePictureRenderer::MultiCorePictureRenderer(int threadCount)
+: fNumThreads(threadCount)
+, fThreadPool(threadCount)
+, fCountdown(threadCount) {
+ // Only need to create fNumThreads - 1 clones, since one thread will use the base
+ // picture.
+ fPictureClones = SkNEW_ARRAY(SkPicture, fNumThreads - 1);
+ fCloneData = SkNEW_ARRAY(CloneData*, fNumThreads);
+}
+
+void MultiCorePictureRenderer::init(SkPicture *pict) {
+ // Set fPicture and the tiles.
+ this->INHERITED::init(pict);
+ for (int i = 0; i < fNumThreads; ++i) {
+ *fCanvasPool.append() = this->setupCanvas(this->getTileWidth(), this->getTileHeight());
+ }
+ // Only need to create fNumThreads - 1 clones, since one thread will use the base picture.
+ fPicture->clone(fPictureClones, fNumThreads - 1);
+ // Populate each thread with the appropriate data.
+ // Group the tiles into nearly equal size chunks, rounding up so we're sure to cover them all.
+ const int chunkSize = (fTileRects.count() + fNumThreads - 1) / fNumThreads;
+
+ for (int i = 0; i < fNumThreads; i++) {
+ SkPicture* pic;
+ if (i == fNumThreads-1) {
+ // The last set will use the original SkPicture.
+ pic = fPicture;
+ } else {
+ pic = &fPictureClones[i];
+ }
+ const int start = i * chunkSize;
+ const int end = SkMin32(start + chunkSize, fTileRects.count());
+ fCloneData[i] = SkNEW_ARGS(CloneData,
+ (pic, fCanvasPool[i], fTileRects, start, end, &fCountdown));
+ }
+}
+
+bool MultiCorePictureRenderer::render(const SkString *path, SkBitmap** out) {
+ bool success = true;
+ if (path != NULL) {
+ for (int i = 0; i < fNumThreads-1; i++) {
+ fCloneData[i]->setPathAndSuccess(path, &success);
+ }
+ }
+
+ if (NULL != out) {
+ *out = SkNEW(SkBitmap);
+ setup_bitmap(*out, fPicture->width(), fPicture->height());
+ for (int i = 0; i < fNumThreads; i++) {
+ fCloneData[i]->setBitmap(*out);
+ }
+ } else {
+ for (int i = 0; i < fNumThreads; i++) {
+ fCloneData[i]->setBitmap(NULL);
+ }
+ }
+
+ fCountdown.reset(fNumThreads);
+ for (int i = 0; i < fNumThreads; i++) {
+ fThreadPool.add(fCloneData[i]);
+ }
+ fCountdown.wait();
+
+ return success;
+}
+
+void MultiCorePictureRenderer::end() {
+ for (int i = 0; i < fNumThreads - 1; i++) {
+ SkDELETE(fCloneData[i]);
+ fCloneData[i] = NULL;
+ }
+
+ fCanvasPool.unrefAll();
+
+ this->INHERITED::end();
+}
+
+MultiCorePictureRenderer::~MultiCorePictureRenderer() {
+ // Each individual CloneData was deleted in end.
+ SkDELETE_ARRAY(fCloneData);
+ SkDELETE_ARRAY(fPictureClones);
+}
+
+SkString MultiCorePictureRenderer::getConfigNameInternal() {
+ SkString name = this->INHERITED::getConfigNameInternal();
+ name.appendf("_multi_%i_threads", fNumThreads);
+ return name;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+void PlaybackCreationRenderer::setup() {
+ fReplayer.reset(this->createPicture());
+ SkCanvas* recorder = fReplayer->beginRecording(this->getViewWidth(), this->getViewHeight(),
+ this->recordFlags());
+ this->scaleToScaleFactor(recorder);
+ fPicture->draw(recorder);
+}
+
+bool PlaybackCreationRenderer::render(const SkString*, SkBitmap** out) {
+ fReplayer->endRecording();
+ // Since this class does not actually render, return false.
+ return false;
+}
+
+SkString PlaybackCreationRenderer::getConfigNameInternal() {
+ return SkString("playback_creation");
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+// SkPicture variants for each BBoxHierarchy type
+
+class RTreePicture : public SkPicture {
+public:
+ virtual SkBBoxHierarchy* createBBoxHierarchy() const SK_OVERRIDE{
+ static const int kRTreeMinChildren = 6;
+ static const int kRTreeMaxChildren = 11;
+ SkScalar aspectRatio = SkScalarDiv(SkIntToScalar(fWidth),
+ SkIntToScalar(fHeight));
+ return SkRTree::Create(kRTreeMinChildren, kRTreeMaxChildren,
+ aspectRatio);
+ }
+};
+
+SkPicture* PictureRenderer::createPicture() {
+ switch (fBBoxHierarchyType) {
+ case kNone_BBoxHierarchyType:
+ return SkNEW(SkPicture);
+ case kRTree_BBoxHierarchyType:
+ return SkNEW(RTreePicture);
+ case kTileGrid_BBoxHierarchyType:
+ return SkNEW_ARGS(SkTileGridPicture, (fGridWidth, fGridHeight, fPicture->width(),
+ fPicture->height()));
+ }
+ SkASSERT(0); // invalid bbhType
+ return NULL;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class GatherRenderer : public PictureRenderer {
+public:
+ virtual bool render(const SkString* path, SkBitmap** out = NULL)
+ SK_OVERRIDE {
+ SkRect bounds = SkRect::MakeWH(SkIntToScalar(fPicture->width()),
+ SkIntToScalar(fPicture->height()));
+ SkData* data = SkPictureUtils::GatherPixelRefs(fPicture, bounds);
+ SkSafeUnref(data);
+
+ return NULL == path; // we don't have anything to write
+ }
+
+private:
+ virtual SkString getConfigNameInternal() SK_OVERRIDE {
+ return SkString("gather_pixelrefs");
+ }
+};
+
+PictureRenderer* CreateGatherPixelRefsRenderer() {
+ return SkNEW(GatherRenderer);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class PictureCloneRenderer : public PictureRenderer {
+public:
+ virtual bool render(const SkString* path, SkBitmap** out = NULL)
+ SK_OVERRIDE {
+ for (int i = 0; i < 100; ++i) {
+ SkPicture* clone = fPicture->clone();
+ SkSafeUnref(clone);
+ }
+
+ return NULL == path; // we don't have anything to write
+ }
+
+private:
+ virtual SkString getConfigNameInternal() SK_OVERRIDE {
+ return SkString("picture_clone");
+ }
+};
+
+PictureRenderer* CreatePictureCloneRenderer() {
+ return SkNEW(PictureCloneRenderer);
+}
+
+} // namespace sk_tools
diff --git a/tools/PictureRenderer.h b/tools/PictureRenderer.h
new file mode 100644
index 0000000000..5d6c5166a2
--- /dev/null
+++ b/tools/PictureRenderer.h
@@ -0,0 +1,457 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef PictureRenderer_DEFINED
+#define PictureRenderer_DEFINED
+
+#include "SkCountdown.h"
+#include "SkDrawFilter.h"
+#include "SkMath.h"
+#include "SkPaint.h"
+#include "SkPicture.h"
+#include "SkRect.h"
+#include "SkRefCnt.h"
+#include "SkRunnable.h"
+#include "SkString.h"
+#include "SkTDArray.h"
+#include "SkThreadPool.h"
+#include "SkTypes.h"
+
+#if SK_SUPPORT_GPU
+#include "GrContextFactory.h"
+#include "GrContext.h"
+#endif
+
+class SkBitmap;
+class SkCanvas;
+class SkGLContext;
+class SkThread;
+
+namespace sk_tools {
+
+class TiledPictureRenderer;
+
+class PictureRenderer : public SkRefCnt {
+
+public:
+ enum SkDeviceTypes {
+ kBitmap_DeviceType,
+#if SK_SUPPORT_GPU
+ kGPU_DeviceType
+#endif
+ };
+
+ enum BBoxHierarchyType {
+ kNone_BBoxHierarchyType = 0,
+ kRTree_BBoxHierarchyType,
+ kTileGrid_BBoxHierarchyType,
+ };
+
+ // this uses SkPaint::Flags as a base and adds additional flags
+ enum DrawFilterFlags {
+ kNone_DrawFilterFlag = 0,
+ kBlur_DrawFilterFlag = 0x4000, // toggles between blur and no blur
+ kHinting_DrawFilterFlag = 0x8000, // toggles between no hinting and normal hinting
+ kSlightHinting_DrawFilterFlag = 0x10000, // toggles between slight and normal hinting
+ kAAClip_DrawFilterFlag = 0x20000, // toggles between soft and hard clip
+ };
+
+ SK_COMPILE_ASSERT(!(kBlur_DrawFilterFlag & SkPaint::kAllFlags), blur_flag_must_be_greater);
+ SK_COMPILE_ASSERT(!(kHinting_DrawFilterFlag & SkPaint::kAllFlags),
+ hinting_flag_must_be_greater);
+ SK_COMPILE_ASSERT(!(kSlightHinting_DrawFilterFlag & SkPaint::kAllFlags),
+ slight_hinting_flag_must_be_greater);
+
+ /**
+ * Called with each new SkPicture to render.
+ */
+ virtual void init(SkPicture* pict);
+
+ /**
+ * Set the viewport so that only the portion listed gets drawn.
+ */
+ void setViewport(SkISize size) { fViewport = size; }
+
+ /**
+ * Set the scale factor at which draw the picture.
+ */
+ void setScaleFactor(SkScalar scale) { fScaleFactor = scale; }
+
+ /**
+ * Perform any setup that should done prior to each iteration of render() which should not be
+ * timed.
+ */
+ virtual void setup() {}
+
+ /**
+ * Perform work that is to be timed. Typically this is rendering, but is also used for recording
+ * and preparing picture for playback by the subclasses which do those.
+ * If path is non-null, subclass implementations should call write().
+ * @param path If non-null, also write the output to the file specified by path. path should
+ * have no extension; it will be added by write().
+ * @return bool True if rendering succeeded and, if path is non-null, the output was
+ * successfully written to a file.
+ */
+ virtual bool render(const SkString* path, SkBitmap** out = NULL) = 0;
+
+ /**
+ * Called once finished with a particular SkPicture, before calling init again, and before
+ * being done with this Renderer.
+ */
+ virtual void end();
+
+ /**
+ * If this PictureRenderer is actually a TiledPictureRender, return a pointer to this as a
+ * TiledPictureRender so its methods can be called.
+ */
+ virtual TiledPictureRenderer* getTiledRenderer() { return NULL; }
+
+ void resetState();
+
+ void setDeviceType(SkDeviceTypes deviceType) {
+ fDeviceType = deviceType;
+ }
+
+ void setDrawFilters(DrawFilterFlags const * const filters, const SkString& configName) {
+ memcpy(fDrawFilters, filters, sizeof(fDrawFilters));
+ fDrawFiltersConfig = configName;
+ }
+
+ void setBBoxHierarchyType(BBoxHierarchyType bbhType) {
+ fBBoxHierarchyType = bbhType;
+ }
+
+ void setGridSize(int width, int height) {
+ fGridWidth = width;
+ fGridHeight = height;
+ }
+
+ bool isUsingBitmapDevice() {
+ return kBitmap_DeviceType == fDeviceType;
+ }
+
+ virtual SkString getPerIterTimeFormat() { return SkString("%.2f"); }
+
+ virtual SkString getNormalTimeFormat() { return SkString("%6.2f"); }
+
+ /**
+ * Reports the configuration of this PictureRenderer.
+ */
+ SkString getConfigName() {
+ SkString config = this->getConfigNameInternal();
+ if (!fViewport.isEmpty()) {
+ config.appendf("_viewport_%ix%i", fViewport.width(), fViewport.height());
+ }
+ if (kRTree_BBoxHierarchyType == fBBoxHierarchyType) {
+ config.append("_rtree");
+ } else if (kTileGrid_BBoxHierarchyType == fBBoxHierarchyType) {
+ config.append("_grid");
+ }
+#if SK_SUPPORT_GPU
+ if (this->isUsingGpuDevice()) {
+ config.append("_gpu");
+ }
+#endif
+ config.append(fDrawFiltersConfig.c_str());
+ return config;
+ }
+
+#if SK_SUPPORT_GPU
+ bool isUsingGpuDevice() {
+ return kGPU_DeviceType == fDeviceType;
+ }
+
+ SkGLContext* getGLContext() {
+ if (this->isUsingGpuDevice()) {
+ return fGrContextFactory.getGLContext(GrContextFactory::kNative_GLContextType);
+ } else {
+ return NULL;
+ }
+ }
+
+ GrContext* getGrContext() {
+ return fGrContext;
+ }
+#endif
+
+ PictureRenderer()
+ : fPicture(NULL)
+ , fDeviceType(kBitmap_DeviceType)
+ , fBBoxHierarchyType(kNone_BBoxHierarchyType)
+ , fGridWidth(0)
+ , fGridHeight(0)
+ , fScaleFactor(SK_Scalar1)
+#if SK_SUPPORT_GPU
+ , fGrContext(fGrContextFactory.get(GrContextFactory::kNative_GLContextType))
+#endif
+ {
+ sk_bzero(fDrawFilters, sizeof(fDrawFilters));
+ fViewport.set(0, 0);
+ }
+
+protected:
+ SkAutoTUnref<SkCanvas> fCanvas;
+ SkPicture* fPicture;
+ SkDeviceTypes fDeviceType;
+ BBoxHierarchyType fBBoxHierarchyType;
+ DrawFilterFlags fDrawFilters[SkDrawFilter::kTypeCount];
+ SkString fDrawFiltersConfig;
+ int fGridWidth, fGridHeight; // used when fBBoxHierarchyType is TileGrid
+
+#if SK_SUPPORT_GPU
+ GrContextFactory fGrContextFactory;
+ GrContext* fGrContext;
+#endif
+
+ void buildBBoxHierarchy();
+
+ /**
+ * Return the total width that should be drawn. If the viewport width has been set greater than
+ * 0, this will be the minimum of the current SkPicture's width and the viewport's width.
+ */
+ int getViewWidth();
+
+ /**
+ * Return the total height that should be drawn. If the viewport height has been set greater
+ * than 0, this will be the minimum of the current SkPicture's height and the viewport's height.
+ */
+ int getViewHeight();
+
+ /**
+ * Scales the provided canvas to the scale factor set by setScaleFactor.
+ */
+ void scaleToScaleFactor(SkCanvas*);
+
+ SkPicture* createPicture();
+ uint32_t recordFlags();
+ SkCanvas* setupCanvas();
+ virtual SkCanvas* setupCanvas(int width, int height);
+
+private:
+ SkISize fViewport;
+ SkScalar fScaleFactor;
+
+ virtual SkString getConfigNameInternal() = 0;
+
+ typedef SkRefCnt INHERITED;
+};
+
+/**
+ * This class does not do any rendering, but its render function executes recording, which we want
+ * to time.
+ */
+class RecordPictureRenderer : public PictureRenderer {
+ virtual bool render(const SkString*, SkBitmap** out = NULL) SK_OVERRIDE;
+
+ virtual SkString getPerIterTimeFormat() SK_OVERRIDE { return SkString("%.4f"); }
+
+ virtual SkString getNormalTimeFormat() SK_OVERRIDE { return SkString("%6.4f"); }
+
+protected:
+ virtual SkCanvas* setupCanvas(int width, int height) SK_OVERRIDE;
+
+private:
+ virtual SkString getConfigNameInternal() SK_OVERRIDE;
+};
+
+class PipePictureRenderer : public PictureRenderer {
+public:
+ virtual bool render(const SkString*, SkBitmap** out = NULL) SK_OVERRIDE;
+
+private:
+ virtual SkString getConfigNameInternal() SK_OVERRIDE;
+
+ typedef PictureRenderer INHERITED;
+};
+
+class SimplePictureRenderer : public PictureRenderer {
+public:
+ virtual void init(SkPicture* pict) SK_OVERRIDE;
+
+ virtual bool render(const SkString*, SkBitmap** out = NULL) SK_OVERRIDE;
+
+private:
+ virtual SkString getConfigNameInternal() SK_OVERRIDE;
+
+ typedef PictureRenderer INHERITED;
+};
+
+class TiledPictureRenderer : public PictureRenderer {
+public:
+ TiledPictureRenderer();
+
+ virtual void init(SkPicture* pict) SK_OVERRIDE;
+
+ /**
+ * Renders to tiles, rather than a single canvas. If a path is provided, a separate file is
+ * created for each tile, named "path0.png", "path1.png", etc.
+ * Multithreaded mode currently does not support writing to a file.
+ */
+ virtual bool render(const SkString* path, SkBitmap** out = NULL) SK_OVERRIDE;
+
+ virtual void end() SK_OVERRIDE;
+
+ void setTileWidth(int width) {
+ fTileWidth = width;
+ }
+
+ int getTileWidth() const {
+ return fTileWidth;
+ }
+
+ void setTileHeight(int height) {
+ fTileHeight = height;
+ }
+
+ int getTileHeight() const {
+ return fTileHeight;
+ }
+
+ void setTileWidthPercentage(double percentage) {
+ fTileWidthPercentage = percentage;
+ }
+
+ double getTileWidthPercentage() const {
+ return fTileWidthPercentage;
+ }
+
+ void setTileHeightPercentage(double percentage) {
+ fTileHeightPercentage = percentage;
+ }
+
+ double getTileHeightPercentage() const {
+ return fTileHeightPercentage;
+ }
+
+ void setTileMinPowerOf2Width(int width) {
+ SkASSERT(SkIsPow2(width) && width > 0);
+ if (!SkIsPow2(width) || width <= 0) {
+ return;
+ }
+
+ fTileMinPowerOf2Width = width;
+ }
+
+ int getTileMinPowerOf2Width() const {
+ return fTileMinPowerOf2Width;
+ }
+
+ virtual TiledPictureRenderer* getTiledRenderer() SK_OVERRIDE { return this; }
+
+ /**
+ * Report the number of tiles in the x and y directions. Must not be called before init.
+ * @param x Output parameter identifying the number of tiles in the x direction.
+ * @param y Output parameter identifying the number of tiles in the y direction.
+ * @return True if the tiles have been set up, and x and y are meaningful. If false, x and y are
+ * unmodified.
+ */
+ bool tileDimensions(int& x, int&y);
+
+ /**
+ * Move to the next tile and return its indices. Must be called before calling drawCurrentTile
+ * for the first time.
+ * @param i Output parameter identifying the column of the next tile to be drawn on the next
+ * call to drawNextTile.
+ * @param j Output parameter identifying the row of the next tile to be drawn on the next call
+ * to drawNextTile.
+ * @param True if the tiles have been created and the next tile to be drawn by drawCurrentTile
+ * is within the range of tiles. If false, i and j are unmodified.
+ */
+ bool nextTile(int& i, int& j);
+
+ /**
+ * Render one tile. This will draw the same tile each time it is called until nextTile is
+ * called. The tile rendered will depend on how many calls have been made to nextTile.
+ * It is an error to call this without first calling nextTile, or if nextTile returns false.
+ */
+ void drawCurrentTile();
+
+protected:
+ SkTDArray<SkRect> fTileRects;
+
+ virtual SkCanvas* setupCanvas(int width, int height) SK_OVERRIDE;
+ virtual SkString getConfigNameInternal() SK_OVERRIDE;
+
+private:
+ int fTileWidth;
+ int fTileHeight;
+ double fTileWidthPercentage;
+ double fTileHeightPercentage;
+ int fTileMinPowerOf2Width;
+
+ // These variables are only used for timing individual tiles.
+ // Next tile to draw in fTileRects.
+ int fCurrentTileOffset;
+ // Number of tiles in the x direction.
+ int fTilesX;
+ // Number of tiles in the y direction.
+ int fTilesY;
+
+ void setupTiles();
+ void setupPowerOf2Tiles();
+
+ typedef PictureRenderer INHERITED;
+};
+
+class CloneData;
+
+class MultiCorePictureRenderer : public TiledPictureRenderer {
+public:
+ explicit MultiCorePictureRenderer(int threadCount);
+
+ ~MultiCorePictureRenderer();
+
+ virtual void init(SkPicture* pict) SK_OVERRIDE;
+
+ /**
+ * Behaves like TiledPictureRenderer::render(), only using multiple threads.
+ */
+ virtual bool render(const SkString* path, SkBitmap** out = NULL) SK_OVERRIDE;
+
+ virtual void end() SK_OVERRIDE;
+
+private:
+ virtual SkString getConfigNameInternal() SK_OVERRIDE;
+
+ const int fNumThreads;
+ SkTDArray<SkCanvas*> fCanvasPool;
+ SkThreadPool fThreadPool;
+ SkPicture* fPictureClones;
+ CloneData** fCloneData;
+ SkCountdown fCountdown;
+
+ typedef TiledPictureRenderer INHERITED;
+};
+
+/**
+ * This class does not do any rendering, but its render function executes turning an SkPictureRecord
+ * into an SkPicturePlayback, which we want to time.
+ */
+class PlaybackCreationRenderer : public PictureRenderer {
+public:
+ virtual void setup() SK_OVERRIDE;
+
+ virtual bool render(const SkString*, SkBitmap** out = NULL) SK_OVERRIDE;
+
+ virtual SkString getPerIterTimeFormat() SK_OVERRIDE { return SkString("%.4f"); }
+
+ virtual SkString getNormalTimeFormat() SK_OVERRIDE { return SkString("%6.4f"); }
+
+private:
+ SkAutoTUnref<SkPicture> fReplayer;
+
+ virtual SkString getConfigNameInternal() SK_OVERRIDE;
+
+ typedef PictureRenderer INHERITED;
+};
+
+extern PictureRenderer* CreateGatherPixelRefsRenderer();
+extern PictureRenderer* CreatePictureCloneRenderer();
+
+}
+
+#endif // PictureRenderer_DEFINED
diff --git a/tools/__init__.py b/tools/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tools/__init__.py
diff --git a/tools/bench_pictures.cfg b/tools/bench_pictures.cfg
new file mode 100644
index 0000000000..c64a686640
--- /dev/null
+++ b/tools/bench_pictures.cfg
@@ -0,0 +1,117 @@
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+"""
+This file defines the configurations in which bench_pictures should be run
+on various platforms. The buildbots read these configurations from the
+bench_pictures_cfg dictionary. Everything else in this file exists to help in
+constructing that dictionary.
+
+This code is executed directly on the buildbot so that convenient things like
+variables and loops can be used to avoid unnecessary verbosity. With great power
+comes great responsibility; don't put any nasty code here. To reiterate, code in
+this file will be directly executed on the build slaves.
+"""
+
+
+import os
+import sys
+
+
+if 'import_path' in globals():
+ sys.path.append(import_path)
+
+
+from bench_pictures_cfg_helper import *
+
+
+# Default tile sizes
+DEFAULT_TILE_X = '256'
+DEFAULT_TILE_Y = '256'
+
+
+# Configs to run on most bots
+default_configs = [
+ # Basic CPU and GPU configs
+ TiledBitmapConfig(DEFAULT_TILE_X, DEFAULT_TILE_Y),
+ TiledGPUConfig(DEFAULT_TILE_X, DEFAULT_TILE_Y),
+
+ # CopyTiles
+ CopyTilesConfig(DEFAULT_TILE_X, DEFAULT_TILE_Y),
+
+ # Record
+ RecordConfig(),
+
+ # Multi-threaded
+ MultiThreadTileConfig(2, DEFAULT_TILE_X, DEFAULT_TILE_Y),
+ MultiThreadTileConfig(3, DEFAULT_TILE_X, DEFAULT_TILE_Y),
+ MultiThreadTileConfig(4, DEFAULT_TILE_X, DEFAULT_TILE_Y),
+
+ # Different tile sizes
+ TiledBitmapConfig(512, 512),
+ TiledBitmapConfig(1024, 256),
+ TiledBitmapConfig(1024, 64),
+
+ # Different bounding box heirarchies, for different modes.
+ RecordRTreeConfig( DEFAULT_TILE_X, DEFAULT_TILE_Y),
+ RecordGridConfig( DEFAULT_TILE_X, DEFAULT_TILE_Y),
+ PlaybackCreationRTreeConfig(DEFAULT_TILE_X, DEFAULT_TILE_Y),
+ PlaybackCreationGridConfig( DEFAULT_TILE_X, DEFAULT_TILE_Y),
+ TileRTreeConfig( DEFAULT_TILE_X, DEFAULT_TILE_Y),
+ TileGridConfig( DEFAULT_TILE_X, DEFAULT_TILE_Y),
+]
+
+
+def AndroidConfigList(tile_size, scale, cores, viewport, do_gpu=True):
+ tile_x = tile_size[0]
+ tile_y = tile_size[1]
+
+ viewport_x = viewport[0]
+ viewport_y = viewport[1]
+
+ configs = [
+ # Record
+ RecordConfig(scale=str(scale)),
+ RecordRTreeConfig(tile_x, tile_y, scale=str(scale)),
+ RecordGridConfig( tile_x, tile_y, scale=str(scale)),
+
+ # Tiled playback
+ TiledBitmapConfig(tile_x, tile_y, scale=str(scale)),
+ TileRTreeConfig( tile_x, tile_y, scale=str(scale)),
+ TileGridConfig( tile_x, tile_y, scale=str(scale)),
+
+ # Viewport playback
+ ViewportBitmapConfig(viewport_x, viewport_y, scale=str(scale)),
+ ViewportRTreeConfig( viewport_x, viewport_y, scale=str(scale)),
+ ViewportGridConfig( viewport_x, viewport_y, scale=str(scale)),
+ ]
+
+ if do_gpu:
+ configs.append(TiledGPUConfig(tile_x, tile_y, scale=str(scale)))
+ configs.append(ViewportGPUConfig(viewport_x, viewport_y, scale=str(scale)))
+
+ # Multicore
+ for num_cores in cores:
+ configs.append(MultiThreadTileConfig(num_cores, tile_x, tile_y,
+ scale=str(scale)))
+
+ return configs
+
+
+# This dictionary defines the sets of configs for all platforms. Each config is
+# a dictionary of key/value pairs directly corresponding to the command-line
+# flags passed to bench_pictures.
+bench_pictures_cfg = {
+ 'debug': [TiledBitmapConfig(DEFAULT_TILE_X, DEFAULT_TILE_Y)],
+ 'default': default_configs,
+ 'no_gpu': [cfg for cfg in default_configs if cfg['device'] != 'gpu'],
+ 'nexus_s': AndroidConfigList((256, 256), 0.4897, [], (800, 480),
+ do_gpu=False),
+ 'nexus_4': AndroidConfigList((256, 256), 0.8163, [], (1280, 768)),
+ 'nexus_7': AndroidConfigList((256, 256), 0.8163, [2], (1280, 800)),
+ 'nexus_10': AndroidConfigList((512, 512), 1.6326, [], (2560, 1600)),
+ 'galaxy_nexus': AndroidConfigList((256, 256), 0.8163, [], (1280, 800)),
+ 'xoom': AndroidConfigList((256, 256), 0.8163, [], (1200, 800)),
+} \ No newline at end of file
diff --git a/tools/bench_pictures_cfg_helper.py b/tools/bench_pictures_cfg_helper.py
new file mode 100644
index 0000000000..654bad6e66
--- /dev/null
+++ b/tools/bench_pictures_cfg_helper.py
@@ -0,0 +1,101 @@
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+""" Helper functions to be used in bench_pictures.cfg. """
+
+
+def Config(**kwargs):
+ config = {}
+ for key in kwargs:
+ config[key] = kwargs[key]
+ return config
+
+
+def BitmapConfig(**kwargs):
+ return Config(device='bitmap', **kwargs)
+
+
+def GPUConfig(**kwargs):
+ return Config(device='gpu', **kwargs)
+
+
+def TiledBitmapConfig(tile_x, tile_y, **kwargs):
+ return BitmapConfig(mode=['tile', str(tile_x), str(tile_y)], **kwargs)
+
+
+def TiledGPUConfig(tile_x, tile_y, **kwargs):
+ return GPUConfig(mode=['tile', str(tile_x), str(tile_y)], **kwargs)
+
+
+def ViewportBitmapConfig(viewport_x, viewport_y, **kwargs):
+ return BitmapConfig(viewport=[str(viewport_x), str(viewport_y)], **kwargs)
+
+
+def ViewportGPUConfig(viewport_x, viewport_y, **kwargs):
+ return GPUConfig(viewport=[str(viewport_x), str(viewport_y)], **kwargs)
+
+
+def ViewportRTreeConfig(viewport_x, viewport_y, **kwargs):
+ return RTreeConfig(viewport_x, viewport_y, mode='simple',
+ viewport=[str(viewport_x), str(viewport_y)], **kwargs)
+
+
+def ViewportGridConfig(viewport_x, viewport_y, **kwargs):
+ return GridConfig(viewport_x, viewport_y, mode='simple',
+ viewport=[str(viewport_x), str(viewport_y)], **kwargs)
+
+
+def CopyTilesConfig(tile_x, tile_y, **kwargs):
+ return BitmapConfig(mode=['copyTile', str(tile_x), str(tile_y)], **kwargs)
+
+
+def RecordConfig(**kwargs):
+ return BitmapConfig(mode='record', **kwargs)
+
+
+def PlaybackCreationConfig(**kwargs):
+ return BitmapConfig(mode='playbackCreation', **kwargs)
+
+
+def MultiThreadTileConfig(threads, tile_x, tile_y, **kwargs):
+ return TiledBitmapConfig(multi=str(threads), tile_x=tile_x, tile_y=tile_y,
+ **kwargs)
+
+
+def RTreeConfig(tile_x, tile_y, mode, **kwargs):
+ return BitmapConfig(mode=mode, bbh=['rtree', str(tile_x), str(tile_y)],
+ **kwargs)
+
+
+def GridConfig(tile_x, tile_y, mode, **kwargs):
+ return BitmapConfig(mode=mode, bbh=['grid', str(tile_x), str(tile_y)],
+ **kwargs)
+
+
+def RecordRTreeConfig(tile_x, tile_y, **kwargs):
+ return RTreeConfig(tile_x=tile_x, tile_y=tile_y, mode='record', **kwargs)
+
+
+def PlaybackCreationRTreeConfig(tile_x, tile_y, **kwargs):
+ return RTreeConfig(tile_x=tile_x, tile_y=tile_y, mode='playbackCreation',
+ **kwargs)
+
+
+def TileRTreeConfig(tile_x, tile_y, **kwargs):
+ return RTreeConfig(tile_x=tile_x, tile_y=tile_y,
+ mode=['tile', str(tile_x), str(tile_y)], **kwargs)
+
+
+def RecordGridConfig(tile_x, tile_y, **kwargs):
+ return GridConfig(tile_x=tile_x, tile_y=tile_y, mode='record', **kwargs)
+
+
+def PlaybackCreationGridConfig(tile_x, tile_y, **kwargs):
+ return GridConfig(tile_x, tile_y, mode='playbackCreation')
+
+
+def TileGridConfig(tile_x, tile_y, **kwargs):
+ return GridConfig(tile_x, tile_y, mode=['tile', str(tile_x), str(tile_y)],
+ **kwargs) \ No newline at end of file
diff --git a/tools/bench_pictures_main.cpp b/tools/bench_pictures_main.cpp
new file mode 100644
index 0000000000..65fc42ad36
--- /dev/null
+++ b/tools/bench_pictures_main.cpp
@@ -0,0 +1,805 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "BenchTimer.h"
+#include "CopyTilesRenderer.h"
+#include "PictureBenchmark.h"
+#include "SkBenchLogger.h"
+#include "SkCanvas.h"
+#include "SkGraphics.h"
+#include "SkImageDecoder.h"
+#include "SkMath.h"
+#include "SkOSFile.h"
+#include "SkPicture.h"
+#include "SkStream.h"
+#include "SkTArray.h"
+#include "picture_utils.h"
+
+const int DEFAULT_REPEATS = 1;
+
+static char const * const gFilterTypes[] = {
+ "paint",
+ "point",
+ "line",
+ "bitmap",
+ "rect",
+ "path",
+ "text",
+ "all",
+};
+
+static const size_t kFilterTypesCount = sizeof(gFilterTypes) / sizeof(gFilterTypes[0]);
+
+static char const * const gFilterFlags[] = {
+ "antiAlias",
+ "filterBitmap",
+ "dither",
+ "underlineText",
+ "strikeThruText",
+ "fakeBoldText",
+ "linearText",
+ "subpixelText",
+ "devKernText",
+ "LCDRenderText",
+ "embeddedBitmapText",
+ "autoHinting",
+ "verticalText",
+ "genA8FromLCD",
+ "blur",
+ "hinting",
+ "slightHinting",
+ "AAClip",
+};
+
+static const size_t kFilterFlagsCount = sizeof(gFilterFlags) / sizeof(gFilterFlags[0]);
+
+static SkString filtersName(sk_tools::PictureRenderer::DrawFilterFlags* drawFilters) {
+ int all = drawFilters[0];
+ size_t tIndex;
+ for (tIndex = 1; tIndex < SkDrawFilter::kTypeCount; ++tIndex) {
+ all &= drawFilters[tIndex];
+ }
+ SkString result;
+ for (size_t fIndex = 0; fIndex < kFilterFlagsCount; ++fIndex) {
+ SkString types;
+ if (all & (1 << fIndex)) {
+ types = gFilterTypes[SkDrawFilter::kTypeCount];
+ } else {
+ for (tIndex = 0; tIndex < SkDrawFilter::kTypeCount; ++tIndex) {
+ if (drawFilters[tIndex] & (1 << fIndex)) {
+ types += gFilterTypes[tIndex];
+ }
+ }
+ }
+ if (!types.size()) {
+ continue;
+ }
+ result += "_";
+ result += types;
+ result += ".";
+ result += gFilterFlags[fIndex];
+ }
+ return result;
+}
+
+static SkString filterTypesUsage() {
+ SkString result;
+ for (size_t index = 0; index < kFilterTypesCount; ++index) {
+ result += gFilterTypes[index];
+ if (index < kFilterTypesCount - 1) {
+ result += " | ";
+ }
+ }
+ return result;
+}
+
+static SkString filterFlagsUsage() {
+ SkString result;
+ size_t len = 0;
+ for (size_t index = 0; index < kFilterFlagsCount; ++index) {
+ result += gFilterFlags[index];
+ if (result.size() - len >= 72) {
+ result += "\n ";
+ len = result.size();
+ }
+ if (index < kFilterFlagsCount - 1) {
+ result += " | ";
+ }
+ }
+ return result;
+}
+
+static void usage(const char* argv0) {
+ SkDebugf("SkPicture benchmarking tool\n");
+ SkDebugf("\n"
+"Usage: \n"
+" %s <inputDir>...\n"
+" [--logFile filename][--timers [wcgWC]*][--logPerIter 1|0][--min]\n"
+" [--repeat][--timeIndividualTiles] \n"
+" [--mode pow2tile minWidth height | record | simple\n"
+" | tile width height | playbackCreation]\n"
+" [--pipe]\n"
+" [--bbh bbhType]\n"
+" [--multi numThreads]\n"
+" [--viewport width height][--scale sf]\n"
+" [--device bitmap"
+#if SK_SUPPORT_GPU
+" | gpu"
+#endif
+"]\n"
+" [--filter [%s]:\n [%s]]\n"
+, argv0, filterTypesUsage().c_str(), filterFlagsUsage().c_str());
+ SkDebugf("\n");
+ SkDebugf(
+" inputDir: A list of directories and files to use as input. Files are\n"
+" expected to have the .skp extension.\n\n"
+" --logFile filename : destination for writing log output, in addition to stdout.\n");
+ SkDebugf(" --logPerIter 1|0 : "
+ "Log each repeat timer instead of mean, default is disabled.\n");
+ SkDebugf(" --min : Print the minimum times (instead of average).\n");
+ SkDebugf(" --timers [wcgWC]* : "
+ "Display wall, cpu, gpu, truncated wall or truncated cpu time for each picture.\n");
+ SkDebugf(" --timeIndividualTiles : Report times for drawing individual tiles, rather than\n"
+" times for drawing the whole page.\n"
+" Requires --mode tile\n");
+ SkDebugf(
+" --mode pow2tile minWidth height | copyTile width height | record | simple\n"
+" | tile width height | playbackCreation:\n"
+" Run in the corresponding mode.\n"
+" Default is simple.\n");
+ SkDebugf(
+" pow2tile minWidth height, Creates tiles with widths\n"
+" that are all a power of two\n"
+" such that they minimize the\n"
+" amount of wasted tile space.\n"
+" minWidth is the minimum width\n"
+" of these tiles and must be a\n"
+" power of two. Simple\n"
+" rendering using these tiles\n"
+" is benchmarked.\n");
+ SkDebugf(
+" record, Benchmark picture to picture recording.\n");
+ SkDebugf(
+" simple, Benchmark a simple rendering.\n");
+ SkDebugf(
+" tile width height, Benchmark simple rendering using\n"
+" tiles with the given dimensions.\n"
+" copyTile width height, Draw the picture, then copy it into tiles.\n"
+" Does not support percentages.\n"
+" If the picture is large enough, breaks it into\n"
+" larger tiles (and draws the picture once per\n"
+" larger tile) to avoid creating a large canvas.\n"
+" Add --tiles x y to specify the number of tiles\n"
+" per larger tile in the x and y direction.\n"
+ );
+ SkDebugf(
+" playbackCreation, Benchmark creation of the SkPicturePlayback.\n");
+ SkDebugf("\n");
+ SkDebugf(
+" --multi numThreads : Set the number of threads for multi threaded drawing. Must be greater\n"
+" than 1. Only works with tiled rendering.\n"
+" --viewport width height : Set the viewport.\n"
+" --scale sf : Scale drawing by sf.\n"
+" --pipe: Benchmark SkGPipe rendering. Currently incompatible with \"mode\".\n");
+ SkDebugf(
+" --bbh bbhType [width height]: Set the bounding box hierarchy type to\n"
+" be used. Accepted values are: none, rtree, grid. Default\n"
+" value is none. Not compatible with --pipe. With value\n"
+" 'grid', width and height must be specified. 'grid' can\n"
+" only be used with modes tile, record, and\n"
+" playbackCreation.");
+ SkDebugf(
+" --device bitmap"
+#if SK_SUPPORT_GPU
+" | gpu"
+#endif
+": Use the corresponding device. Default is bitmap.\n");
+ SkDebugf(
+" bitmap, Render to a bitmap.\n");
+#if SK_SUPPORT_GPU
+ SkDebugf(
+" gpu, Render to the GPU.\n");
+#endif
+ SkDebugf("\n");
+ SkDebugf(
+" --repeat: "
+"Set the number of times to repeat each test."
+" Default is %i.\n", DEFAULT_REPEATS);
+ SkDebugf(
+" --filter type:flag : Enable canvas filtering to disable a paint flag,\n"
+" use no blur or low quality blur, or use no hinting or\n"
+" slight hinting. For all flags except AAClip, specify the\n"
+" type of primitive to effect, or choose all. for AAClip\n"
+" alone, the filter affects all clips independent of type.\n");
+}
+
+SkBenchLogger gLogger;
+
+static bool run_single_benchmark(const SkString& inputPath,
+ sk_tools::PictureBenchmark& benchmark) {
+ SkFILEStream inputStream;
+
+ inputStream.setPath(inputPath.c_str());
+ if (!inputStream.isValid()) {
+ SkString err;
+ err.printf("Could not open file %s\n", inputPath.c_str());
+ gLogger.logError(err);
+ return false;
+ }
+
+ bool success = false;
+ SkPicture picture(&inputStream, &success, &SkImageDecoder::DecodeStream);
+ if (!success) {
+ SkString err;
+ err.printf("Could not read an SkPicture from %s\n", inputPath.c_str());
+ gLogger.logError(err);
+ return false;
+ }
+
+ SkString filename;
+ sk_tools::get_basename(&filename, inputPath);
+
+ SkString result;
+ result.printf("running bench [%i %i] %s ", picture.width(),
+ picture.height(), filename.c_str());
+ gLogger.logProgress(result);
+
+ benchmark.run(&picture);
+ return true;
+}
+
+#define PRINT_USAGE_AND_EXIT \
+ do { \
+ usage(argv0); \
+ exit(-1); \
+ } while (0)
+
+static void parse_commandline(int argc, char* const argv[], SkTArray<SkString>* inputs,
+ sk_tools::PictureBenchmark* benchmark) {
+ const char* argv0 = argv[0];
+ char* const* stop = argv + argc;
+
+ int repeats = DEFAULT_REPEATS;
+ sk_tools::PictureRenderer::SkDeviceTypes deviceType =
+ sk_tools::PictureRenderer::kBitmap_DeviceType;
+
+ SkAutoTUnref<sk_tools::PictureRenderer> renderer(NULL);
+
+ // Create a string to show our current settings.
+ // TODO: Make it prettier. Currently it just repeats the command line.
+ SkString commandLine("bench_pictures:");
+ for (int i = 1; i < argc; i++) {
+ commandLine.appendf(" %s", *(argv+i));
+ }
+ commandLine.append("\n");
+
+ bool usePipe = false;
+ int numThreads = 1;
+ bool useTiles = false;
+ const char* widthString = NULL;
+ const char* heightString = NULL;
+ int gridWidth = 0;
+ int gridHeight = 0;
+ bool isPowerOf2Mode = false;
+ bool isCopyMode = false;
+ const char* xTilesString = NULL;
+ const char* yTilesString = NULL;
+ const char* mode = NULL;
+ bool gridSupported = false;
+ sk_tools::PictureRenderer::BBoxHierarchyType bbhType =
+ sk_tools::PictureRenderer::kNone_BBoxHierarchyType;
+ sk_tools::PictureRenderer::DrawFilterFlags drawFilters[SkDrawFilter::kTypeCount];
+ sk_bzero(drawFilters, sizeof(drawFilters));
+ SkISize viewport;
+ viewport.setEmpty();
+ SkScalar scaleFactor = SK_Scalar1;
+ for (++argv; argv < stop; ++argv) {
+ if (0 == strcmp(*argv, "--repeat")) {
+ ++argv;
+ if (argv < stop) {
+ repeats = atoi(*argv);
+ if (repeats < 1) {
+ gLogger.logError("--repeat must be given a value > 0\n");
+ PRINT_USAGE_AND_EXIT;
+ }
+ } else {
+ gLogger.logError("Missing arg for --repeat\n");
+ PRINT_USAGE_AND_EXIT;
+ }
+ } else if (0 == strcmp(*argv, "--pipe")) {
+ usePipe = true;
+ } else if (0 == strcmp(*argv, "--logFile")) {
+ argv++;
+ if (argv < stop) {
+ if (!gLogger.SetLogFile(*argv)) {
+ SkString str;
+ str.printf("Could not open %s for writing.", *argv);
+ gLogger.logError(str);
+ usage(argv0);
+ // TODO(borenet): We're disabling this for now, due to
+ // write-protected Android devices. The very short-term
+ // solution is to ignore the fact that we have no log file.
+ //exit(-1);
+ }
+ } else {
+ gLogger.logError("Missing arg for --logFile\n");
+ PRINT_USAGE_AND_EXIT;
+ }
+ } else if (0 == strcmp(*argv, "--multi")) {
+ ++argv;
+ if (argv >= stop) {
+ gLogger.logError("Missing arg for --multi\n");
+ PRINT_USAGE_AND_EXIT;
+ }
+ numThreads = atoi(*argv);
+ if (numThreads < 2) {
+ gLogger.logError("Number of threads must be at least 2.\n");
+ PRINT_USAGE_AND_EXIT;
+ }
+ } else if (0 == strcmp(*argv, "--bbh")) {
+ ++argv;
+ if (argv >= stop) {
+ gLogger.logError("Missing value for --bbh\n");
+ PRINT_USAGE_AND_EXIT;
+ }
+ if (0 == strcmp(*argv, "none")) {
+ bbhType = sk_tools::PictureRenderer::kNone_BBoxHierarchyType;
+ } else if (0 == strcmp(*argv, "rtree")) {
+ bbhType = sk_tools::PictureRenderer::kRTree_BBoxHierarchyType;
+ } else if (0 == strcmp(*argv, "grid")) {
+ bbhType = sk_tools::PictureRenderer::kTileGrid_BBoxHierarchyType;
+ ++argv;
+ if (argv >= stop) {
+ gLogger.logError("Missing width for --bbh grid\n");
+ PRINT_USAGE_AND_EXIT;
+ }
+ gridWidth = atoi(*argv);
+ ++argv;
+ if (argv >= stop) {
+ gLogger.logError("Missing height for --bbh grid\n");
+ PRINT_USAGE_AND_EXIT;
+ }
+ gridHeight = atoi(*argv);
+ } else {
+ SkString err;
+ err.printf("%s is not a valid value for --bbhType\n", *argv);
+ gLogger.logError(err);
+ PRINT_USAGE_AND_EXIT;
+ }
+
+ } else if (0 == strcmp(*argv, "--mode")) {
+ if (renderer.get() != NULL) {
+ SkDebugf("Cannot combine modes.\n");
+ PRINT_USAGE_AND_EXIT;
+ }
+
+ ++argv;
+ if (argv >= stop) {
+ gLogger.logError("Missing mode for --mode\n");
+ PRINT_USAGE_AND_EXIT;
+ }
+
+ if (0 == strcmp(*argv, "record")) {
+ renderer.reset(SkNEW(sk_tools::RecordPictureRenderer));
+ gridSupported = true;
+ } else if (0 == strcmp(*argv, "clone")) {
+ renderer.reset(sk_tools::CreatePictureCloneRenderer());
+ } else if (0 == strcmp(*argv, "simple")) {
+ renderer.reset(SkNEW(sk_tools::SimplePictureRenderer));
+ } else if ((0 == strcmp(*argv, "tile")) || (0 == strcmp(*argv, "pow2tile"))
+ || 0 == strcmp(*argv, "copyTile")) {
+ useTiles = true;
+ mode = *argv;
+
+ if (0 == strcmp(*argv, "pow2tile")) {
+ isPowerOf2Mode = true;
+ } else if (0 == strcmp(*argv, "copyTile")) {
+ isCopyMode = true;
+ } else {
+ gridSupported = true;
+ }
+
+ ++argv;
+ if (argv >= stop) {
+ SkString err;
+ err.printf("Missing width for --mode %s\n", mode);
+ gLogger.logError(err);
+ PRINT_USAGE_AND_EXIT;
+ }
+
+ widthString = *argv;
+ ++argv;
+ if (argv >= stop) {
+ SkString err;
+ err.appendf("Missing height for --mode %s\n", mode);
+ gLogger.logError(err);
+ PRINT_USAGE_AND_EXIT;
+ }
+ heightString = *argv;
+ } else if (0 == strcmp(*argv, "playbackCreation")) {
+ renderer.reset(SkNEW(sk_tools::PlaybackCreationRenderer));
+ gridSupported = true;
+ } else if (0 == strcmp(*argv, "gatherPixelRefs")) {
+ renderer.reset(sk_tools::CreateGatherPixelRefsRenderer());
+ } else {
+ SkString err;
+ err.printf("%s is not a valid mode for --mode\n", *argv);
+ gLogger.logError(err);
+ PRINT_USAGE_AND_EXIT;
+ }
+ } else if (0 == strcmp(*argv, "--viewport")) {
+ ++argv;
+ if (argv >= stop) {
+ gLogger.logError("Missing width for --viewport\n");
+ PRINT_USAGE_AND_EXIT;
+ }
+ viewport.fWidth = atoi(*argv);
+ ++argv;
+ if (argv >= stop) {
+ gLogger.logError("Missing height for --viewport\n");
+ PRINT_USAGE_AND_EXIT;
+ }
+ viewport.fHeight = atoi(*argv);
+ } else if (0 == strcmp(*argv, "--scale")) {
+ ++argv;
+ if (argv >= stop) {
+ gLogger.logError("Missing scaleFactor for --scale\n");
+ PRINT_USAGE_AND_EXIT;
+ }
+ scaleFactor = atof(*argv);
+ } else if (0 == strcmp(*argv, "--tiles")) {
+ ++argv;
+ if (argv >= stop) {
+ gLogger.logError("Missing x for --tiles\n");
+ PRINT_USAGE_AND_EXIT;
+ }
+ xTilesString = *argv;
+ ++argv;
+ if (argv >= stop) {
+ gLogger.logError("Missing y for --tiles\n");
+ PRINT_USAGE_AND_EXIT;
+ }
+ yTilesString = *argv;
+ } else if (0 == strcmp(*argv, "--device")) {
+ ++argv;
+ if (argv >= stop) {
+ gLogger.logError("Missing mode for --device\n");
+ PRINT_USAGE_AND_EXIT;
+ }
+
+ if (0 == strcmp(*argv, "bitmap")) {
+ deviceType = sk_tools::PictureRenderer::kBitmap_DeviceType;
+ }
+#if SK_SUPPORT_GPU
+ else if (0 == strcmp(*argv, "gpu")) {
+ deviceType = sk_tools::PictureRenderer::kGPU_DeviceType;
+ }
+#endif
+ else {
+ SkString err;
+ err.printf("%s is not a valid mode for --device\n", *argv);
+ gLogger.logError(err);
+ PRINT_USAGE_AND_EXIT;
+ }
+ } else if (0 == strcmp(*argv, "--timers")) {
+ ++argv;
+ if (argv < stop) {
+ bool timerWall = false;
+ bool truncatedTimerWall = false;
+ bool timerCpu = false;
+ bool truncatedTimerCpu = false;
+ bool timerGpu = false;
+ for (char* t = *argv; *t; ++t) {
+ switch (*t) {
+ case 'w':
+ timerWall = true;
+ break;
+ case 'c':
+ timerCpu = true;
+ break;
+ case 'W':
+ truncatedTimerWall = true;
+ break;
+ case 'C':
+ truncatedTimerCpu = true;
+ break;
+ case 'g':
+ timerGpu = true;
+ break;
+ default: {
+ break;
+ }
+ }
+ }
+ benchmark->setTimersToShow(timerWall, truncatedTimerWall, timerCpu,
+ truncatedTimerCpu, timerGpu);
+ } else {
+ gLogger.logError("Missing arg for --timers\n");
+ PRINT_USAGE_AND_EXIT;
+ }
+ } else if (0 == strcmp(*argv, "--timeIndividualTiles")) {
+ benchmark->setTimeIndividualTiles(true);
+ } else if (0 == strcmp(*argv, "--min")) {
+ benchmark->setPrintMin(true);
+ } else if (0 == strcmp(*argv, "--logPerIter")) {
+ ++argv;
+ if (argv < stop) {
+ bool log = atoi(*argv) != 0;
+ benchmark->setLogPerIter(log);
+ } else {
+ gLogger.logError("Missing arg for --logPerIter\n");
+ PRINT_USAGE_AND_EXIT;
+ }
+ } else if (0 == strcmp(*argv, "--filter")) {
+ ++argv;
+ if (argv < stop) {
+ const char* colon = strchr(*argv, ':');
+ if (colon) {
+ int type = -1;
+ size_t typeLen = colon - *argv;
+ for (size_t tIndex = 0; tIndex < kFilterTypesCount; ++tIndex) {
+ if (typeLen == strlen(gFilterTypes[tIndex])
+ && !strncmp(*argv, gFilterTypes[tIndex], typeLen)) {
+ type = tIndex;
+ break;
+ }
+ }
+ if (type < 0) {
+ SkString err;
+ err.printf("Unknown type for --filter %s\n", *argv);
+ gLogger.logError(err);
+ PRINT_USAGE_AND_EXIT;
+ }
+ int flag = -1;
+ size_t flagLen = strlen(*argv) - typeLen - 1;
+ for (size_t fIndex = 0; fIndex < kFilterFlagsCount; ++fIndex) {
+ if (flagLen == strlen(gFilterFlags[fIndex])
+ && !strncmp(colon + 1, gFilterFlags[fIndex], flagLen)) {
+ flag = 1 << fIndex;
+ break;
+ }
+ }
+ if (flag < 0) {
+ SkString err;
+ err.printf("Unknown flag for --filter %s\n", *argv);
+ gLogger.logError(err);
+ PRINT_USAGE_AND_EXIT;
+ }
+ for (int index = 0; index < SkDrawFilter::kTypeCount; ++index) {
+ if (type != SkDrawFilter::kTypeCount && index != type) {
+ continue;
+ }
+ drawFilters[index] = (sk_tools::PictureRenderer::DrawFilterFlags)
+ (drawFilters[index] | flag);
+ }
+ } else {
+ SkString err;
+ err.printf("Unknown arg for --filter %s : missing colon\n", *argv);
+ gLogger.logError(err);
+ PRINT_USAGE_AND_EXIT;
+ }
+ } else {
+ gLogger.logError("Missing arg for --filter\n");
+ PRINT_USAGE_AND_EXIT;
+ }
+ } else if (0 == strcmp(*argv, "--help") || 0 == strcmp(*argv, "-h")) {
+ PRINT_USAGE_AND_EXIT;
+ } else {
+ inputs->push_back(SkString(*argv));
+ }
+ }
+
+ if (numThreads > 1 && !useTiles) {
+ gLogger.logError("Multithreaded drawing requires tiled rendering.\n");
+ PRINT_USAGE_AND_EXIT;
+ }
+
+ if (usePipe && sk_tools::PictureRenderer::kNone_BBoxHierarchyType != bbhType) {
+ gLogger.logError("--pipe and --bbh cannot be used together\n");
+ PRINT_USAGE_AND_EXIT;
+ }
+
+ if (sk_tools::PictureRenderer::kTileGrid_BBoxHierarchyType == bbhType &&
+ !gridSupported) {
+ gLogger.logError("'--bbh grid' is not compatible with specified --mode.\n");
+ PRINT_USAGE_AND_EXIT;
+ }
+
+ if (useTiles) {
+ SkASSERT(NULL == renderer);
+ sk_tools::TiledPictureRenderer* tiledRenderer;
+ if (isCopyMode) {
+ int x, y;
+ if (xTilesString != NULL) {
+ SkASSERT(yTilesString != NULL);
+ x = atoi(xTilesString);
+ y = atoi(yTilesString);
+ if (x <= 0 || y <= 0) {
+ gLogger.logError("--tiles must be given values > 0\n");
+ PRINT_USAGE_AND_EXIT;
+ }
+ } else {
+ x = y = 4;
+ }
+ tiledRenderer = SkNEW_ARGS(sk_tools::CopyTilesRenderer, (x, y));
+ if (benchmark->timeIndividualTiles()) {
+ gLogger.logError("timeIndividualTiles is not compatible with copyTile\n");
+ PRINT_USAGE_AND_EXIT;
+ }
+ } else if (numThreads > 1) {
+ tiledRenderer = SkNEW_ARGS(sk_tools::MultiCorePictureRenderer, (numThreads));
+ } else {
+ tiledRenderer = SkNEW(sk_tools::TiledPictureRenderer);
+ }
+ if (isPowerOf2Mode) {
+ int minWidth = atoi(widthString);
+ if (!SkIsPow2(minWidth) || minWidth < 0) {
+ tiledRenderer->unref();
+ SkString err;
+ err.printf("-mode %s must be given a width"
+ " value that is a power of two\n", mode);
+ gLogger.logError(err);
+ PRINT_USAGE_AND_EXIT;
+ }
+ tiledRenderer->setTileMinPowerOf2Width(minWidth);
+ } else if (sk_tools::is_percentage(widthString)) {
+ if (isCopyMode) {
+ tiledRenderer->unref();
+ SkString err;
+ err.printf("--mode %s does not support percentages.\n", mode);
+ gLogger.logError(err.c_str());
+ PRINT_USAGE_AND_EXIT;
+ }
+ tiledRenderer->setTileWidthPercentage(atof(widthString));
+ if (!(tiledRenderer->getTileWidthPercentage() > 0)) {
+ tiledRenderer->unref();
+ SkString err;
+ err.appendf("--mode %s must be given a width percentage > 0\n", mode);
+ gLogger.logError(err);
+ PRINT_USAGE_AND_EXIT;
+ }
+ } else {
+ tiledRenderer->setTileWidth(atoi(widthString));
+ if (!(tiledRenderer->getTileWidth() > 0)) {
+ tiledRenderer->unref();
+ SkString err;
+ err.appendf("--mode %s must be given a width > 0\n", mode);
+ gLogger.logError(err);
+ PRINT_USAGE_AND_EXIT;
+ }
+ }
+
+ if (sk_tools::is_percentage(heightString)) {
+ if (isCopyMode) {
+ tiledRenderer->unref();
+ SkString err;
+ err.printf("--mode %s does not support percentages.\n", mode);
+ gLogger.logError(err.c_str());
+ PRINT_USAGE_AND_EXIT;
+ }
+ tiledRenderer->setTileHeightPercentage(atof(heightString));
+ if (!(tiledRenderer->getTileHeightPercentage() > 0)) {
+ tiledRenderer->unref();
+ SkString err;
+ err.appendf("--mode %s must be given a height percentage > 0\n", mode);
+ gLogger.logError(err);
+ PRINT_USAGE_AND_EXIT;
+ }
+ } else {
+ tiledRenderer->setTileHeight(atoi(heightString));
+ if (!(tiledRenderer->getTileHeight() > 0)) {
+ tiledRenderer->unref();
+ SkString err;
+ err.appendf("--mode %s must be given a height > 0\n", mode);
+ gLogger.logError(err);
+ PRINT_USAGE_AND_EXIT;
+ }
+ }
+ if (numThreads > 1) {
+#if SK_SUPPORT_GPU
+ if (sk_tools::PictureRenderer::kGPU_DeviceType == deviceType) {
+ tiledRenderer->unref();
+ gLogger.logError("GPU not compatible with multithreaded tiling.\n");
+ PRINT_USAGE_AND_EXIT;
+ }
+#endif
+ }
+ renderer.reset(tiledRenderer);
+ if (usePipe) {
+ gLogger.logError("Pipe rendering is currently not compatible with tiling.\n"
+ "Turning off pipe.\n");
+ }
+ } else {
+ if (benchmark->timeIndividualTiles()) {
+ gLogger.logError("timeIndividualTiles requires tiled rendering.\n");
+ PRINT_USAGE_AND_EXIT;
+ }
+ if (usePipe) {
+ if (renderer.get() != NULL) {
+ gLogger.logError("Pipe is incompatible with other modes.\n");
+ PRINT_USAGE_AND_EXIT;
+ }
+ renderer.reset(SkNEW(sk_tools::PipePictureRenderer));
+ }
+ }
+ if (inputs->count() < 1) {
+ PRINT_USAGE_AND_EXIT;
+ }
+
+ if (NULL == renderer) {
+ renderer.reset(SkNEW(sk_tools::SimplePictureRenderer));
+ }
+
+ renderer->setBBoxHierarchyType(bbhType);
+ renderer->setDrawFilters(drawFilters, filtersName(drawFilters));
+ renderer->setGridSize(gridWidth, gridHeight);
+ renderer->setViewport(viewport);
+ renderer->setScaleFactor(scaleFactor);
+ benchmark->setRenderer(renderer);
+ benchmark->setRepeats(repeats);
+ benchmark->setDeviceType(deviceType);
+ benchmark->setLogger(&gLogger);
+ // Report current settings:
+ gLogger.logProgress(commandLine);
+}
+
+static int process_input(const SkString& input,
+ sk_tools::PictureBenchmark& benchmark) {
+ SkOSFile::Iter iter(input.c_str(), "skp");
+ SkString inputFilename;
+ int failures = 0;
+ if (iter.next(&inputFilename)) {
+ do {
+ SkString inputPath;
+ sk_tools::make_filepath(&inputPath, input, inputFilename);
+ if (!run_single_benchmark(inputPath, benchmark)) {
+ ++failures;
+ }
+ } while(iter.next(&inputFilename));
+ } else if (SkStrEndsWith(input.c_str(), ".skp")) {
+ if (!run_single_benchmark(input, benchmark)) {
+ ++failures;
+ }
+ } else {
+ SkString warning;
+ warning.printf("Warning: skipping %s\n", input.c_str());
+ gLogger.logError(warning);
+ }
+ return failures;
+}
+
+int tool_main(int argc, char** argv);
+int tool_main(int argc, char** argv) {
+#ifdef SK_ENABLE_INST_COUNT
+ gPrintInstCount = true;
+#endif
+ SkAutoGraphics ag;
+
+ SkTArray<SkString> inputs;
+ sk_tools::PictureBenchmark benchmark;
+
+ parse_commandline(argc, argv, &inputs, &benchmark);
+
+ int failures = 0;
+ for (int i = 0; i < inputs.count(); ++i) {
+ failures += process_input(inputs[i], benchmark);
+ }
+
+ if (failures != 0) {
+ SkString err;
+ err.printf("Failed to run %i benchmarks.\n", failures);
+ gLogger.logError(err);
+ return 1;
+ }
+ return 0;
+}
+
+#if !defined SK_BUILD_FOR_IOS
+int main(int argc, char * const argv[]) {
+ return tool_main(argc, (char**) argv);
+}
+#endif
diff --git a/tools/filtermain.cpp b/tools/filtermain.cpp
new file mode 100644
index 0000000000..c1b2040fd0
--- /dev/null
+++ b/tools/filtermain.cpp
@@ -0,0 +1,320 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkDevice.h"
+#include "SkGraphics.h"
+#include "SkImageDecoder.h"
+#include "SkImageEncoder.h"
+#include "SkOSFile.h"
+#include "SkPicture.h"
+#include "SkPicturePlayback.h"
+#include "SkPictureRecord.h"
+#include "SkStream.h"
+#include "picture_utils.h"
+#include "path_utils.h"
+
+static void usage() {
+ SkDebugf("Usage: filter -i inFile [-o outFile] [--input-dir path] [--output-dir path]\n");
+ SkDebugf(" [-p pathFile] [-t textureDir] [-h|--help]\n\n");
+ SkDebugf(" -i inFile : file to file.\n");
+ SkDebugf(" -o outFile : result of filtering.\n");
+ SkDebugf(" --input-dir : process all files in dir with .skp extension.\n");
+ SkDebugf(" --output-dir : results of filtering the input dir.\n");
+ SkDebugf(" -p pathFile : file in which to place compileable path data.\n");
+ SkDebugf(" -t textureDir : directory in which to place textures. (only available w/ single file)\n");
+ SkDebugf(" -h|--help : Show this help message.\n");
+}
+
+// SkFilterRecord allows the filter to manipulate the read in SkPicture
+class SkFilterRecord : public SkPictureRecord {
+public:
+ SkFilterRecord(uint32_t recordFlags, SkDevice* device, SkFILEWStream* pathStream)
+ : INHERITED(recordFlags, device)
+ , fTransSkipped(0)
+ , fTransTot(0)
+ , fScalesSkipped(0)
+ , fScalesTot(0)
+ , fPathStream(pathStream) {
+ }
+
+ virtual ~SkFilterRecord() {
+ }
+
+ virtual bool clipPath(const SkPath& path, SkRegion::Op op, bool doAntiAlias) SK_OVERRIDE {
+ if (!path.isRect(NULL) && 4 < path.countPoints()) {
+ sk_tools::dump_path(fPathStream, path);
+ }
+ return INHERITED::clipPath(path, op, doAntiAlias);
+ }
+
+ virtual void drawPath(const SkPath& path, const SkPaint& p) SK_OVERRIDE {
+ if (!path.isRect(NULL) && 4 < path.countPoints()) {
+ sk_tools::dump_path(fPathStream, path);
+ }
+ INHERITED::drawPath(path, p);
+ }
+
+ virtual bool translate(SkScalar dx, SkScalar dy) SK_OVERRIDE {
+ ++fTransTot;
+
+#if 0
+ if (0 == dx && 0 == dy) {
+ ++fTransSkipped;
+ return true;
+ }
+#endif
+
+ return INHERITED::translate(dx, dy);
+ }
+
+ virtual bool scale(SkScalar sx, SkScalar sy) SK_OVERRIDE {
+ ++fScalesTot;
+
+#if 0
+ if (SK_Scalar1 == sx && SK_Scalar1 == sy) {
+ ++fScalesSkipped;
+ return true;
+ }
+#endif
+
+ return INHERITED::scale(sx, sy);
+ }
+
+ void saveImages(const SkString& path) {
+ SkTRefArray<SkBitmap>* bitmaps = fBitmapHeap->extractBitmaps();
+
+ if (NULL != bitmaps) {
+ for (int i = 0; i < bitmaps->count(); ++i) {
+ SkString filename(path);
+ if (!path.endsWith("\\")) {
+ filename.append("\\");
+ }
+ filename.append("image");
+ filename.appendS32(i);
+ filename.append(".png");
+
+ SkImageEncoder::EncodeFile(filename.c_str(), (*bitmaps)[i],
+ SkImageEncoder::kPNG_Type, 0);
+ }
+ }
+
+ bitmaps->unref();
+ }
+
+ void report() {
+ SkDebugf("%d Trans skipped (out of %d)\n", fTransSkipped, fTransTot);
+ SkDebugf("%d Scales skipped (out of %d)\n", fScalesSkipped, fScalesTot);
+ }
+
+protected:
+ int fTransSkipped;
+ int fTransTot;
+
+ int fScalesSkipped;
+ int fScalesTot;
+
+ SkFILEWStream* fPathStream;
+private:
+ typedef SkPictureRecord INHERITED;
+};
+
+// Wrap SkPicture to allow installation of a SkFilterRecord object
+class SkFilterPicture : public SkPicture {
+public:
+ SkFilterPicture(int width, int height, SkPictureRecord* record) {
+ fWidth = width;
+ fHeight = height;
+ fRecord = record;
+ SkSafeRef(fRecord);
+ }
+
+private:
+ typedef SkPicture INHERITED;
+};
+
+static bool PNGEncodeBitmapToStream(SkWStream* stream, const SkBitmap& bitmap) {
+ return SkImageEncoder::EncodeStream(stream, bitmap, SkImageEncoder::kPNG_Type, 100);
+}
+
+int filter_picture(const SkString& inFile, const SkString& outFile,
+ const SkString& textureDir, SkFILEWStream *pathStream) {
+ SkPicture* inPicture = NULL;
+
+ SkFILEStream inStream(inFile.c_str());
+ if (inStream.isValid()) {
+ inPicture = SkNEW_ARGS(SkPicture, (&inStream, NULL, &SkImageDecoder::DecodeStream));
+ }
+
+ if (NULL == inPicture) {
+ SkDebugf("Could not read file %s\n", inFile.c_str());
+ return -1;
+ }
+
+ SkBitmap bm;
+ bm.setConfig(SkBitmap::kNo_Config, inPicture->width(), inPicture->height());
+ SkAutoTUnref<SkDevice> dev(SkNEW_ARGS(SkDevice, (bm)));
+
+ SkAutoTUnref<SkFilterRecord> filterRecord(SkNEW_ARGS(SkFilterRecord, (0, dev, pathStream)));
+
+ // Playback the read in picture to the SkFilterRecorder to allow filtering
+ filterRecord->beginRecording();
+ inPicture->draw(filterRecord);
+ filterRecord->endRecording();
+
+ filterRecord->report();
+
+ if (!outFile.isEmpty()) {
+ SkFilterPicture outPicture(inPicture->width(), inPicture->height(), filterRecord);
+ SkFILEWStream outStream(outFile.c_str());
+
+ outPicture.serialize(&outStream);
+ }
+
+ if (!textureDir.isEmpty()) {
+ filterRecord->saveImages(textureDir);
+ }
+
+ return 0;
+}
+
+// This function is not marked as 'static' so it can be referenced externally
+// in the iOS build.
+int tool_main(int argc, char** argv) {
+ SkGraphics::Init();
+
+ if (argc < 3) {
+ usage();
+ return -1;
+ }
+
+ SkString inFile, outFile, inDir, outDir, textureDir, pathFile;
+
+ char* const* stop = argv + argc;
+ for (++argv; argv < stop; ++argv) {
+ if (strcmp(*argv, "-i") == 0) {
+ argv++;
+ if (argv < stop && **argv) {
+ inFile.set(*argv);
+ } else {
+ SkDebugf("missing arg for -i\n");
+ usage();
+ return -1;
+ }
+ } else if (strcmp(*argv, "--input-dir") == 0) {
+ argv++;
+ if (argv < stop && **argv) {
+ inDir.set(*argv);
+ } else {
+ SkDebugf("missing arg for --input-dir\n");
+ usage();
+ return -1;
+ }
+ } else if (strcmp(*argv, "--output-dir") == 0) {
+ argv++;
+ if (argv < stop && **argv) {
+ outDir.set(*argv);
+ } else {
+ SkDebugf("missing arg for --output-dir\n");
+ usage();
+ return -1;
+ }
+ } else if (strcmp(*argv, "-o") == 0) {
+ argv++;
+ if (argv < stop && **argv) {
+ outFile.set(*argv);
+ } else {
+ SkDebugf("missing arg for -o\n");
+ usage();
+ return -1;
+ }
+ } else if (strcmp(*argv, "-p") == 0) {
+ argv++;
+ if (argv < stop && **argv) {
+ pathFile.set(*argv);
+ } else {
+ SkDebugf("missing arg for -p\n");
+ usage();
+ return -1;
+ }
+ } else if (strcmp(*argv, "-t") == 0) {
+ argv++;
+ if (argv < stop && **argv) {
+ textureDir.set(*argv);
+ } else {
+ SkDebugf("missing arg for -t\n");
+ usage();
+ return -1;
+ }
+ } else if (strcmp(*argv, "--help") == 0 || strcmp(*argv, "-h") == 0) {
+ usage();
+ return 0;
+ } else {
+ SkDebugf("unknown arg %s\n", *argv);
+ usage();
+ return -1;
+ }
+ }
+
+ if(!inDir.isEmpty() && !textureDir.isEmpty()) {
+ SkDebugf("ERROR: The textureDir option is not permitted when passing an input directory.\n");
+ usage();
+ return -1;
+ }
+
+ SkFILEWStream *pathStream = NULL;
+
+ if (!pathFile.isEmpty()) {
+ pathStream = new SkFILEWStream(pathFile.c_str());
+ if (!pathStream->isValid()) {
+ SkDebugf("Could open path file %s\n", pathFile.c_str());
+ delete pathStream;
+ return -1;
+ }
+
+ sk_tools::dump_path_prefix(pathStream);
+ }
+
+ SkOSFile::Iter iter(inDir.c_str(), "skp");
+ int failures = 0;
+ SkString inputFilename, outputFilename;
+ if (iter.next(&inputFilename)) {
+
+ do {
+ sk_tools::make_filepath(&inFile, inDir, inputFilename);
+ if (!outDir.isEmpty()) {
+ sk_tools::make_filepath(&outFile, outDir, inputFilename);
+ }
+ SkDebugf("Executing %s\n", inputFilename.c_str());
+ filter_picture(inFile, outFile, textureDir, pathStream);
+ } while(iter.next(&inputFilename));
+
+ } else if (!inFile.isEmpty()) {
+ filter_picture(inFile, outFile, textureDir, pathStream);
+ } else {
+ usage();
+ if (NULL != pathStream) {
+ delete pathStream;
+ pathStream = NULL;
+ }
+ return -1;
+ }
+
+ if (NULL != pathStream) {
+ sk_tools::dump_path_suffix(pathStream);
+ delete pathStream;
+ pathStream = NULL;
+ }
+
+ SkGraphics::Term();
+ return 0;
+}
+
+#if !defined SK_BUILD_FOR_IOS
+int main(int argc, char * const argv[]) {
+ return tool_main(argc, (char**) argv);
+}
+#endif
diff --git a/tools/path_utils.cpp b/tools/path_utils.cpp
new file mode 100644
index 0000000000..b1dad3487f
--- /dev/null
+++ b/tools/path_utils.cpp
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "path_utils.h"
+#include "SkPath.h"
+#include "SkStream.h"
+
+namespace sk_tools {
+ static int gCurPathID = 0;
+
+ void dump_path_prefix(SkFILEWStream* pathStream) {
+ if (NULL == pathStream) {
+ return;
+ }
+
+ pathStream->writeText("#include \"SkScalar.h\"\n");
+ pathStream->writeText("#include \"SkPoint.h\"\n");
+ pathStream->writeText("#include \"SkBitmap.h\"\n");
+ pathStream->writeText("#include \"SkDevice.h\"\n");
+ pathStream->writeText("#include \"SkString.h\"\n");
+ pathStream->writeText("#include \"SkImageEncoder.h\"\n");
+ }
+
+ void dump_path(SkFILEWStream* pathStream, const SkPath& path) {
+ if (NULL == pathStream) {
+ return;
+ }
+
+ static const int kMaxPts = 200;
+ static const int kMaxVerbs = 200;
+
+ int numPts = path.countPoints();
+ int numVerbs = path.countVerbs();
+
+ SkASSERT(numPts <= kMaxPts);
+ SkASSERT(numVerbs <= kMaxVerbs);
+
+ SkPoint pts[kMaxPts];
+ uint8_t verbs[kMaxVerbs];
+
+ path.getPoints(pts, kMaxPts);
+ path.getVerbs(verbs, kMaxVerbs);
+
+ const char* gStrs[] = {
+ "kMove_Verb",
+ "kLine_Verb",
+ "kQuad_Verb",
+ "kCubic_Verb",
+ "kClose_Verb",
+ "kDone_Verb"
+ };
+
+ pathStream->writeText("static const int numPts");
+ pathStream->writeDecAsText(gCurPathID);
+ pathStream->writeText(" = ");
+ pathStream->writeDecAsText(numPts);
+ pathStream->writeText(";\n");
+
+ pathStream->writeText("SkPoint pts");
+ pathStream->writeDecAsText(gCurPathID);
+ pathStream->writeText("[] = {\n");
+
+ for (int i = 0; i < numPts; ++i) {
+ SkString temp;
+
+ pathStream->writeText(" { ");
+ temp.appendScalar(pts[i].fX);
+ temp.append("f, ");
+ temp.appendScalar(pts[i].fY);
+ temp.append("f },\n");
+ pathStream->writeText(temp.c_str());
+ }
+ pathStream->writeText("};\n");
+
+ pathStream->writeText("static const int numVerbs");
+ pathStream->writeDecAsText(gCurPathID);
+ pathStream->writeText(" = ");
+ pathStream->writeDecAsText(numVerbs);
+ pathStream->writeText(";\n");
+
+ pathStream->writeText("uint8_t verbs");
+ pathStream->writeDecAsText(gCurPathID);
+ pathStream->writeText("[] = {\n");
+
+ for (int i = 0; i < numVerbs; ++i) {
+ pathStream->writeText("\tSkPath::");
+ pathStream->writeText(gStrs[verbs[i]]);
+ pathStream->writeText(",\n");
+ }
+ pathStream->writeText("};\n");
+
+ gCurPathID++;
+ }
+
+ void dump_path_suffix(SkFILEWStream* pathStream) {
+ if (NULL == pathStream) {
+ return;
+ }
+
+ pathStream->writeText("int numPaths = ");
+ pathStream->writeDecAsText(gCurPathID);
+ pathStream->writeText(";\n");
+
+ pathStream->writeText("int sizes[] = {\n");
+ for (int i = 0; i < gCurPathID; ++i) {
+ pathStream->writeText("\t numPts");
+ pathStream->writeDecAsText(i);
+ pathStream->writeText(", numVerbs");
+ pathStream->writeDecAsText(i);
+ pathStream->writeText(",\n");
+ }
+ pathStream->writeText("};\n");
+
+ pathStream->writeText("const SkPoint* points[] = {\n");
+ for (int i = 0; i < gCurPathID; ++i) {
+ pathStream->writeText("\tpts");
+ pathStream->writeDecAsText(i);
+ pathStream->writeText(",\n");
+ }
+ pathStream->writeText("};\n");
+
+ pathStream->writeText("const uint8_t* verbs[] = {\n");
+ for (int i = 0; i < gCurPathID; ++i) {
+ pathStream->writeText("\t(const uint8_t*)verbs");
+ pathStream->writeDecAsText(i);
+ pathStream->writeText(",\n");
+ }
+ pathStream->writeText("};\n");
+ }
+}
diff --git a/tools/path_utils.h b/tools/path_utils.h
new file mode 100644
index 0000000000..d05ad16c9a
--- /dev/null
+++ b/tools/path_utils.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef path_utils_DEFINED
+#define path_utils_DEFINED
+
+class SkFILEWStream;
+class SkPath;
+
+namespace sk_tools {
+ // These utilities help write paths to a .cpp file in a compileable form.
+ // To use call them in the order:
+ // dump_path_prefix - once per program invocation
+ // dump_path - once for each path of interest
+ // dump_path_suffix - once per program invocation
+ //
+ // The output system relies on a global current path ID and assumes that
+ // only one set of aggregation arrays will be written per program
+ // invocation. These utilities are not thread safe.
+
+ // Write of the headers needed to compile the resulting .cpp file
+ void dump_path_prefix(SkFILEWStream* pathStream);
+
+ // Write out a single path in the form:
+ // static const int numPts# = ...;
+ // SkPoint pts#[] = { ... };
+ // static const int numVerbs# = ...;
+ // uint8_t verbs#[] = { ... };
+ // Where # is a globally unique identifier
+ void dump_path(SkFILEWStream* pathStream, const SkPath& path);
+
+ // Write out structures to aggregate info about the written paths:
+ // int numPaths = ...;
+ // int sizes[] = {
+ // numPts#, numVerbs#,
+ // ...
+ // };
+ // const SkPoint* points[] = { pts#, ... };
+ // const uint8_t* verbs[] = { verbs#, ... };
+ void dump_path_suffix(SkFILEWStream* pathStream);
+}
+
+#endif
diff --git a/tools/picture_utils.cpp b/tools/picture_utils.cpp
new file mode 100644
index 0000000000..7a5e156fb2
--- /dev/null
+++ b/tools/picture_utils.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "picture_utils.h"
+#include "SkColorPriv.h"
+#include "SkBitmap.h"
+#include "SkPicture.h"
+#include "SkString.h"
+#include "SkStream.h"
+
+namespace sk_tools {
+ void force_all_opaque(const SkBitmap& bitmap) {
+ SkASSERT(NULL == bitmap.getTexture());
+ SkASSERT(SkBitmap::kARGB_8888_Config == bitmap.config());
+ if (NULL != bitmap.getTexture() || SkBitmap::kARGB_8888_Config == bitmap.config()) {
+ return;
+ }
+
+ SkAutoLockPixels lock(bitmap);
+ for (int y = 0; y < bitmap.height(); y++) {
+ for (int x = 0; x < bitmap.width(); x++) {
+ *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
+ }
+ }
+ }
+
+ void make_filepath(SkString* path, const SkString& dir, const SkString& name) {
+ size_t len = dir.size();
+ path->set(dir);
+ if (0 < len && '/' != dir[len - 1]) {
+ path->append("/");
+ }
+ path->append(name);
+ }
+
+ namespace {
+ bool is_path_seperator(const char chr) {
+#if defined(SK_BUILD_FOR_WIN)
+ return chr == '\\' || chr == '/';
+#else
+ return chr == '/';
+#endif
+ }
+ }
+
+ void get_basename(SkString* basename, const SkString& path) {
+ if (path.size() == 0) {
+ basename->reset();
+ return;
+ }
+
+ size_t end = path.size() - 1;
+
+ // Paths pointing to directories often have a trailing slash,
+ // we remove it so the name is not empty
+ if (is_path_seperator(path[end])) {
+ if (end == 0) {
+ basename->reset();
+ return;
+ }
+
+ end -= 1;
+ }
+
+ size_t i = end;
+ do {
+ --i;
+ if (is_path_seperator(path[i])) {
+ const char* basenameStart = path.c_str() + i + 1;
+ size_t basenameLength = end - i;
+ basename->set(basenameStart, basenameLength);
+ return;
+ }
+ } while (i > 0);
+
+ basename->set(path.c_str(), end + 1);
+ }
+
+ bool is_percentage(const char* const string) {
+ SkString skString(string);
+ return skString.endsWith("%");
+ }
+
+ void setup_bitmap(SkBitmap* bitmap, int width, int height) {
+ bitmap->setConfig(SkBitmap::kARGB_8888_Config, width, height);
+ bitmap->allocPixels();
+ bitmap->eraseColor(SK_ColorTRANSPARENT);
+ }
+}
diff --git a/tools/rebaseline.py b/tools/rebaseline.py
new file mode 100755
index 0000000000..21964d4a33
--- /dev/null
+++ b/tools/rebaseline.py
@@ -0,0 +1,77 @@
+#!/usr/bin/python
+
+'''
+Copyright 2012 Google Inc.
+
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+'''
+
+'''
+Rebaselines the given GM tests, on all bots and all configurations.
+Must be run from the gm-expected directory. If run from a git or SVN
+checkout, the files will be added to the staging area for commit.
+'''
+
+import os, subprocess, sys, tempfile
+
+pairs = [
+ ['base-shuttle-win7-intel-float',
+ 'Skia_Shuttle_Win7_Intel_Float_Release_32'],
+ ['base-shuttle-win7-intel-angle',
+ 'Skia_Shuttle_Win7_Intel_Float_ANGLE_Release_32'],
+ ['base-shuttle-win7-intel-directwrite',
+ 'Skia_Shuttle_Win7_Intel_Float_DirectWrite_Release_32'],
+ ['base-shuttle_ubuntu12_ati5770',
+ 'Skia_Shuttle_Ubuntu12_ATI5770_Float_Release_64'],
+ ['base-macmini',
+ 'Skia_Mac_Float_Release_32'],
+ ['base-macmini-lion-float',
+ 'Skia_MacMiniLion_Float_Release_32'],
+ ['base-android-galaxy-nexus',
+ 'Skia_GalaxyNexus_4-1_Float_Release_32'],
+ ['base-android-nexus-7',
+ 'Skia_Nexus7_4-1_Float_Release_32'],
+ ['base-android-nexus-s',
+ 'Skia_NexusS_4-1_Float_Release_32'],
+ ['base-android-xoom',
+ 'Skia_Xoom_4-1_Float_Release_32'],
+]
+
+if len(sys.argv) < 2:
+ print 'Usage: ' + os.path.basename(sys.argv[0]) + ' <testname> '
+ '[ <testname> ... ]'
+ exit(1)
+
+is_svn_checkout = os.path.exists('.svn') or os.path.exists(os.path.join('..', '.svn') )
+is_git_checkout = os.path.exists('.git') or os.path.exists(os.path.join('..', '.git'))
+
+for testname in sys.argv[1:]:
+ for pair in pairs:
+ if (pair[0] == 'base-shuttle-win7-intel-angle'):
+ testtypes = [ 'angle' ]
+ else:
+ testtypes = [ '565', '8888', 'gpu', 'pdf', 'mesa' ]
+ print pair[0] + ':'
+ for testtype in testtypes:
+ infilename = testname + '_' + testtype + '.png'
+ print infilename
+
+ url = 'http://skia-autogen.googlecode.com/svn/gm-actual/' + pair[0] + '/' + pair[1] + '/' + pair[0] + '/' + infilename
+ cmd = [ 'curl', '--fail', '--silent', url ]
+ temp = tempfile.NamedTemporaryFile()
+ ret = subprocess.call(cmd, stdout=temp)
+ if ret != 0:
+ print 'Couldn\'t fetch ' + url
+ continue
+ outfilename = os.path.join(pair[0], infilename);
+ cmd = [ 'cp', temp.name, outfilename ]
+ subprocess.call(cmd);
+ if is_svn_checkout:
+ cmd = [ 'svn', 'add', '--quiet', outfilename ]
+ subprocess.call(cmd)
+ cmd = [ 'svn', 'propset', '--quiet', 'svn:mime-type', 'image/png', outfilename ];
+ subprocess.call(cmd)
+ elif is_git_checkout:
+ cmd = [ 'git', 'add', outfilename ]
+ subprocess.call(cmd)
diff --git a/tools/render_pdfs_main.cpp b/tools/render_pdfs_main.cpp
new file mode 100644
index 0000000000..231f52e9c7
--- /dev/null
+++ b/tools/render_pdfs_main.cpp
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkCanvas.h"
+#include "SkDevice.h"
+#include "SkGraphics.h"
+#include "SkOSFile.h"
+#include "SkPicture.h"
+#include "SkStream.h"
+#include "SkTArray.h"
+#include "PdfRenderer.h"
+#include "picture_utils.h"
+
+/**
+ * render_pdfs
+ *
+ * Given list of directories and files to use as input, expects to find .skp
+ * files and it will convert them to .pdf files writing them in the output
+ * directory.
+ *
+ * Returns zero exit code if all .skp files were converted successfully,
+ * otherwise returns error code 1.
+ */
+
+static const char PDF_FILE_EXTENSION[] = "pdf";
+static const char SKP_FILE_EXTENSION[] = "skp";
+
+static void usage(const char* argv0) {
+ SkDebugf("SKP to PDF rendering tool\n");
+ SkDebugf("\n"
+"Usage: \n"
+" %s <input>... <outputDir> \n"
+, argv0);
+ SkDebugf("\n\n");
+ SkDebugf(
+" input: A list of directories and files to use as input. Files are\n"
+" expected to have the .skp extension.\n\n");
+ SkDebugf(
+" outputDir: directory to write the rendered pdfs.\n\n");
+ SkDebugf("\n");
+}
+
+/** Replaces the extension of a file.
+ * @param path File name whose extension will be changed.
+ * @param old_extension The old extension.
+ * @param new_extension The new extension.
+ * @returns false if the file did not has the expected extension.
+ * if false is returned, contents of path are undefined.
+ */
+static bool replace_filename_extension(SkString* path,
+ const char old_extension[],
+ const char new_extension[]) {
+ if (path->endsWith(old_extension)) {
+ path->remove(path->size() - strlen(old_extension),
+ strlen(old_extension));
+ if (!path->endsWith(".")) {
+ return false;
+ }
+ path->append(new_extension);
+ return true;
+ }
+ return false;
+}
+
+/** Builds the output filename. path = dir/name, and it replaces expected
+ * .skp extension with .pdf extention.
+ * @param path Output filename.
+ * @param name The name of the file.
+ * @returns false if the file did not has the expected extension.
+ * if false is returned, contents of path are undefined.
+ */
+static bool make_output_filepath(SkString* path, const SkString& dir,
+ const SkString& name) {
+ sk_tools::make_filepath(path, dir, name);
+ return replace_filename_extension(path,
+ SKP_FILE_EXTENSION,
+ PDF_FILE_EXTENSION);
+}
+
+/** Write the output of pdf renderer to a file.
+ * @param outputDir Output dir.
+ * @param inputFilename The skp file that was read.
+ * @param renderer The object responsible to write the pdf file.
+ */
+static bool write_output(const SkString& outputDir,
+ const SkString& inputFilename,
+ const sk_tools::PdfRenderer& renderer) {
+ SkString outputPath;
+ if (!make_output_filepath(&outputPath, outputDir, inputFilename)) {
+ return false;
+ }
+ bool isWritten = renderer.write(outputPath);
+ if (!isWritten) {
+ SkDebugf("Could not write to file %s\n", outputPath.c_str());
+ }
+ return isWritten;
+}
+
+/** Reads an skp file, renders it to pdf and writes the output to a pdf file
+ * @param inputPath The skp file to be read.
+ * @param outputDir Output dir.
+ * @param renderer The object responsible to render the skp object into pdf.
+ */
+static bool render_pdf(const SkString& inputPath, const SkString& outputDir,
+ sk_tools::PdfRenderer& renderer) {
+ SkString inputFilename;
+ sk_tools::get_basename(&inputFilename, inputPath);
+
+ SkFILEStream inputStream;
+ inputStream.setPath(inputPath.c_str());
+ if (!inputStream.isValid()) {
+ SkDebugf("Could not open file %s\n", inputPath.c_str());
+ return false;
+ }
+
+ bool success = false;
+ SkAutoTUnref<SkPicture>
+ picture(SkNEW_ARGS(SkPicture, (&inputStream, &success)));
+
+ if (!success) {
+ SkDebugf("Could not read an SkPicture from %s\n", inputPath.c_str());
+ return false;
+ }
+
+ SkDebugf("exporting... [%i %i] %s\n", picture->width(), picture->height(),
+ inputPath.c_str());
+
+ renderer.init(picture);
+
+ renderer.render();
+
+ success = write_output(outputDir, inputFilename, renderer);
+
+ renderer.end();
+ return success;
+}
+
+/** For each file in the directory or for the file passed in input, call
+ * render_pdf.
+ * @param input A directory or an skp file.
+ * @param outputDir Output dir.
+ * @param renderer The object responsible to render the skp object into pdf.
+ */
+static int process_input(const SkString& input, const SkString& outputDir,
+ sk_tools::PdfRenderer& renderer) {
+ int failures = 0;
+ if (sk_isdir(input.c_str())) {
+ SkOSFile::Iter iter(input.c_str(), SKP_FILE_EXTENSION);
+ SkString inputFilename;
+ while (iter.next(&inputFilename)) {
+ SkString inputPath;
+ sk_tools::make_filepath(&inputPath, input, inputFilename);
+ if (!render_pdf(inputPath, outputDir, renderer)) {
+ ++failures;
+ }
+ }
+ } else {
+ SkString inputPath(input);
+ if (!render_pdf(inputPath, outputDir, renderer)) {
+ ++failures;
+ }
+ }
+ return failures;
+}
+
+static void parse_commandline(int argc, char* const argv[],
+ SkTArray<SkString>* inputs) {
+ const char* argv0 = argv[0];
+ char* const* stop = argv + argc;
+
+ for (++argv; argv < stop; ++argv) {
+ if ((0 == strcmp(*argv, "-h")) || (0 == strcmp(*argv, "--help"))) {
+ usage(argv0);
+ exit(-1);
+ } else {
+ inputs->push_back(SkString(*argv));
+ }
+ }
+
+ if (inputs->count() < 2) {
+ usage(argv0);
+ exit(-1);
+ }
+}
+
+int tool_main(int argc, char** argv);
+int tool_main(int argc, char** argv) {
+
+ SkAutoGraphics ag;
+ SkTArray<SkString> inputs;
+
+ SkAutoTUnref<sk_tools::PdfRenderer>
+ renderer(SkNEW(sk_tools::SimplePdfRenderer));
+ SkASSERT(renderer.get());
+
+ parse_commandline(argc, argv, &inputs);
+ SkString outputDir = inputs[inputs.count() - 1];
+
+ int failures = 0;
+ for (int i = 0; i < inputs.count() - 1; i ++) {
+ failures += process_input(inputs[i], outputDir, *renderer);
+ }
+
+ if (failures != 0) {
+ SkDebugf("Failed to render %i PDFs.\n", failures);
+ return 1;
+ }
+}
+
+#if !defined SK_BUILD_FOR_IOS
+int main(int argc, char * const argv[]) {
+ return tool_main(argc, (char**) argv);
+}
+#endif
+
diff --git a/tools/render_pictures_main.cpp b/tools/render_pictures_main.cpp
new file mode 100644
index 0000000000..fce4055139
--- /dev/null
+++ b/tools/render_pictures_main.cpp
@@ -0,0 +1,714 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "CopyTilesRenderer.h"
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkDevice.h"
+#include "SkGraphics.h"
+#include "SkImageDecoder.h"
+#include "SkImageEncoder.h"
+#include "SkMath.h"
+#include "SkOSFile.h"
+#include "SkPicture.h"
+#include "SkStream.h"
+#include "SkString.h"
+#include "SkTArray.h"
+#include "PictureRenderer.h"
+#include "picture_utils.h"
+
+static void usage(const char* argv0) {
+ SkDebugf("SkPicture rendering tool\n");
+ SkDebugf("\n"
+"Usage: \n"
+" %s <input>... \n"
+" [-w <outputDir>]\n"
+" [--mode pow2tile minWidth height | copyTile width height | simple\n"
+" | tile width height]\n"
+" [--pipe]\n"
+" [--bbh bbhType]\n"
+" [--multi count]\n"
+" [--validate]\n"
+" [--writeWholeImage]\n"
+" [--clone n]\n"
+" [--viewport width height][--scale sf]\n"
+" [--device bitmap"
+#if SK_SUPPORT_GPU
+" | gpu"
+#endif
+"]"
+, argv0);
+ SkDebugf("\n\n");
+ SkDebugf(
+" input: A list of directories and files to use as input. Files are\n"
+" expected to have the .skp extension.\n\n");
+ SkDebugf(
+" outputDir: directory to write the rendered images.\n\n");
+ SkDebugf(
+" --mode pow2tile minWidth height | copyTile width height | simple\n"
+" | tile width height | rerecord: Run in the corresponding mode.\n"
+" Default is simple.\n");
+ SkDebugf(
+" pow2tile minWidth height, Creates tiles with widths\n"
+" that are all a power of two\n"
+" such that they minimize the\n"
+" amount of wasted tile space.\n"
+" minWidth is the minimum width\n"
+" of these tiles and must be a\n"
+" power of two. A simple render\n"
+" is done with these tiles.\n");
+ SkDebugf(
+" simple, Render using the default rendering method.\n"
+" rerecord, Record the picture as a new skp, with the bitmaps PNG encoded.\n"
+ );
+ SkDebugf(
+" tile width height, Do a simple render using tiles\n"
+" with the given dimensions.\n"
+" copyTile width height, Draw the picture, then copy it into tiles.\n"
+" Does not support percentages.\n"
+" If the picture is large enough, breaks it into\n"
+" larger tiles (and draws the picture once per\n"
+" larger tile) to avoid creating a large canvas.\n"
+" Add --tiles x y to specify the number of tiles\n"
+" per larger tile in the x and y direction.\n"
+ );
+ SkDebugf("\n");
+ SkDebugf(
+" --multi count : Set the number of threads for multi threaded drawing. Must be greater\n"
+" than 1. Only works with tiled rendering.\n"
+" --viewport width height : Set the viewport.\n"
+" --scale sf : Scale drawing by sf.\n"
+" --pipe: Benchmark SkGPipe rendering. Currently incompatible with \"mode\".\n");
+ SkDebugf(
+" --validate: Verify that the rendered image contains the same pixels as "
+"the picture rendered in simple mode.\n"
+" --writeWholeImage: In tile mode, write the entire rendered image to a "
+"file, instead of an image for each tile.\n");
+ SkDebugf(
+" --clone n: Clone the picture n times before rendering.\n");
+ SkDebugf(
+" --bbh bbhType [width height]: Set the bounding box hierarchy type to\n"
+" be used. Accepted values are: none, rtree, grid. Default\n"
+" value is none. Not compatible with --pipe. With value\n"
+" 'grid', width and height must be specified. 'grid' can\n"
+" only be used with modes tile, record, and\n"
+" playbackCreation.");
+ SkDebugf(
+" --device bitmap"
+#if SK_SUPPORT_GPU
+" | gpu"
+#endif
+": Use the corresponding device. Default is bitmap.\n");
+ SkDebugf(
+" bitmap, Render to a bitmap.\n");
+#if SK_SUPPORT_GPU
+ SkDebugf(
+" gpu, Render to the GPU.\n");
+#endif
+}
+
+static void make_output_filepath(SkString* path, const SkString& dir,
+ const SkString& name) {
+ sk_tools::make_filepath(path, dir, name);
+ // Remove ".skp"
+ path->remove(path->size() - 4, 4);
+}
+
+static bool render_picture(const SkString& inputPath, const SkString* outputDir,
+ sk_tools::PictureRenderer& renderer,
+ SkBitmap** out,
+ int clones) {
+ SkString inputFilename;
+ sk_tools::get_basename(&inputFilename, inputPath);
+
+ SkFILEStream inputStream;
+ inputStream.setPath(inputPath.c_str());
+ if (!inputStream.isValid()) {
+ SkDebugf("Could not open file %s\n", inputPath.c_str());
+ return false;
+ }
+
+ bool success = false;
+ SkPicture* picture = SkNEW_ARGS(SkPicture,
+ (&inputStream, &success, &SkImageDecoder::DecodeStream));
+ if (!success) {
+ SkDebugf("Could not read an SkPicture from %s\n", inputPath.c_str());
+ return false;
+ }
+
+ for (int i = 0; i < clones; ++i) {
+ SkPicture* clone = picture->clone();
+ SkDELETE(picture);
+ picture = clone;
+ }
+
+ SkDebugf("drawing... [%i %i] %s\n", picture->width(), picture->height(),
+ inputPath.c_str());
+
+ renderer.init(picture);
+ renderer.setup();
+
+ SkString* outputPath = NULL;
+ if (NULL != outputDir) {
+ outputPath = SkNEW(SkString);
+ make_output_filepath(outputPath, *outputDir, inputFilename);
+ }
+
+ success = renderer.render(outputPath, out);
+ if (outputPath) {
+ if (!success) {
+ SkDebugf("Could not write to file %s\n", outputPath->c_str());
+ }
+ SkDELETE(outputPath);
+ }
+
+ renderer.resetState();
+
+ renderer.end();
+
+ SkDELETE(picture);
+ return success;
+}
+
+static bool render_picture(const SkString& inputPath, const SkString* outputDir,
+ sk_tools::PictureRenderer& renderer,
+ bool validate,
+ bool writeWholeImage,
+ int clones) {
+ SkBitmap* bitmap = NULL;
+ bool success = render_picture(inputPath,
+ writeWholeImage ? NULL : outputDir,
+ renderer,
+ validate || writeWholeImage ? &bitmap : NULL, clones);
+
+ if (!success || ((validate || writeWholeImage) && bitmap == NULL)) {
+ SkDebugf("Failed to draw the picture.\n");
+ SkDELETE(bitmap);
+ return false;
+ }
+
+ if (validate) {
+ SkBitmap* referenceBitmap = NULL;
+ sk_tools::SimplePictureRenderer referenceRenderer;
+ success = render_picture(inputPath, NULL, referenceRenderer,
+ &referenceBitmap, 0);
+
+ if (!success || !referenceBitmap) {
+ SkDebugf("Failed to draw the reference picture.\n");
+ SkDELETE(bitmap);
+ SkDELETE(referenceBitmap);
+ return false;
+ }
+
+ if (success && (bitmap->width() != referenceBitmap->width())) {
+ SkDebugf("Expected image width: %i, actual image width %i.\n",
+ referenceBitmap->width(), bitmap->width());
+ SkDELETE(bitmap);
+ SkDELETE(referenceBitmap);
+ return false;
+ }
+ if (success && (bitmap->height() != referenceBitmap->height())) {
+ SkDebugf("Expected image height: %i, actual image height %i",
+ referenceBitmap->height(), bitmap->height());
+ SkDELETE(bitmap);
+ SkDELETE(referenceBitmap);
+ return false;
+ }
+
+ for (int y = 0; success && y < bitmap->height(); y++) {
+ for (int x = 0; success && x < bitmap->width(); x++) {
+ if (*referenceBitmap->getAddr32(x, y) != *bitmap->getAddr32(x, y)) {
+ SkDebugf("Expected pixel at (%i %i): 0x%x, actual 0x%x\n",
+ x, y,
+ referenceBitmap->getAddr32(x, y),
+ *bitmap->getAddr32(x, y));
+ SkDELETE(bitmap);
+ SkDELETE(referenceBitmap);
+ return false;
+ }
+ }
+ }
+ SkDELETE(referenceBitmap);
+ }
+
+ if (writeWholeImage) {
+ sk_tools::force_all_opaque(*bitmap);
+ if (NULL != outputDir && writeWholeImage) {
+ SkString inputFilename;
+ sk_tools::get_basename(&inputFilename, inputPath);
+ SkString outputPath;
+ make_output_filepath(&outputPath, *outputDir, inputFilename);
+ outputPath.append(".png");
+ if (!SkImageEncoder::EncodeFile(outputPath.c_str(), *bitmap,
+ SkImageEncoder::kPNG_Type, 100)) {
+ SkDebugf("Failed to draw the picture.\n");
+ success = false;
+ }
+ }
+ }
+ SkDELETE(bitmap);
+
+ return success;
+}
+
+
+static int process_input(const SkString& input, const SkString* outputDir,
+ sk_tools::PictureRenderer& renderer,
+ bool validate, bool writeWholeImage, int clones) {
+ SkOSFile::Iter iter(input.c_str(), "skp");
+ SkString inputFilename;
+ int failures = 0;
+ SkDebugf("process_input, %s\n", input.c_str());
+ if (iter.next(&inputFilename)) {
+ do {
+ SkString inputPath;
+ sk_tools::make_filepath(&inputPath, input, inputFilename);
+ if (!render_picture(inputPath, outputDir, renderer,
+ validate, writeWholeImage, clones)) {
+ ++failures;
+ }
+ } while(iter.next(&inputFilename));
+ } else if (SkStrEndsWith(input.c_str(), ".skp")) {
+ SkString inputPath(input);
+ if (!render_picture(inputPath, outputDir, renderer,
+ validate, writeWholeImage, clones)) {
+ ++failures;
+ }
+ } else {
+ SkString warning;
+ warning.printf("Warning: skipping %s\n", input.c_str());
+ SkDebugf(warning.c_str());
+ }
+ return failures;
+}
+
+static void parse_commandline(int argc, char* const argv[], SkTArray<SkString>* inputs,
+ sk_tools::PictureRenderer*& renderer, SkString*& outputDir,
+ bool* validate, bool* writeWholeImage,
+ int* clones){
+ const char* argv0 = argv[0];
+ char* const* stop = argv + argc;
+
+ sk_tools::PictureRenderer::SkDeviceTypes deviceType =
+ sk_tools::PictureRenderer::kBitmap_DeviceType;
+
+ bool usePipe = false;
+ int numThreads = 1;
+ bool useTiles = false;
+ const char* widthString = NULL;
+ const char* heightString = NULL;
+ int gridWidth = 0;
+ int gridHeight = 0;
+ bool isPowerOf2Mode = false;
+ bool isCopyMode = false;
+ const char* xTilesString = NULL;
+ const char* yTilesString = NULL;
+ const char* mode = NULL;
+ bool gridSupported = false;
+ sk_tools::PictureRenderer::BBoxHierarchyType bbhType =
+ sk_tools::PictureRenderer::kNone_BBoxHierarchyType;
+ *validate = false;
+ *writeWholeImage = false;
+ *clones = 0;
+ SkISize viewport;
+ viewport.setEmpty();
+ SkScalar scaleFactor = SK_Scalar1;
+
+ for (++argv; argv < stop; ++argv) {
+ if (0 == strcmp(*argv, "--mode")) {
+ if (renderer != NULL) {
+ renderer->unref();
+ SkDebugf("Cannot combine modes.\n");
+ usage(argv0);
+ exit(-1);
+ }
+
+ ++argv;
+ if (argv >= stop) {
+ SkDebugf("Missing mode for --mode\n");
+ usage(argv0);
+ exit(-1);
+ }
+
+ if (0 == strcmp(*argv, "simple")) {
+ renderer = SkNEW(sk_tools::SimplePictureRenderer);
+ } else if ((0 == strcmp(*argv, "tile")) || (0 == strcmp(*argv, "pow2tile"))
+ || 0 == strcmp(*argv, "copyTile")) {
+ useTiles = true;
+ mode = *argv;
+
+ if (0 == strcmp(*argv, "pow2tile")) {
+ isPowerOf2Mode = true;
+ } else if (0 == strcmp(*argv, "copyTile")) {
+ isCopyMode = true;
+ } else {
+ gridSupported = true;
+ }
+
+ ++argv;
+ if (argv >= stop) {
+ SkDebugf("Missing width for --mode %s\n", mode);
+ usage(argv0);
+ exit(-1);
+ }
+
+ widthString = *argv;
+ ++argv;
+ if (argv >= stop) {
+ SkDebugf("Missing height for --mode %s\n", mode);
+ usage(argv0);
+ exit(-1);
+ }
+ heightString = *argv;
+ } else if (0 == strcmp(*argv, "rerecord")) {
+ renderer = SkNEW(sk_tools::RecordPictureRenderer);
+ } else {
+ SkDebugf("%s is not a valid mode for --mode\n", *argv);
+ usage(argv0);
+ exit(-1);
+ }
+ } else if (0 == strcmp(*argv, "--bbh")) {
+ ++argv;
+ if (argv >= stop) {
+ SkDebugf("Missing value for --bbh\n");
+ usage(argv0);
+ exit(-1);
+ }
+ if (0 == strcmp(*argv, "none")) {
+ bbhType = sk_tools::PictureRenderer::kNone_BBoxHierarchyType;
+ } else if (0 == strcmp(*argv, "rtree")) {
+ bbhType = sk_tools::PictureRenderer::kRTree_BBoxHierarchyType;
+ } else if (0 == strcmp(*argv, "grid")) {
+ bbhType = sk_tools::PictureRenderer::kTileGrid_BBoxHierarchyType;
+ ++argv;
+ if (argv >= stop) {
+ SkDebugf("Missing width for --bbh grid\n");
+ usage(argv0);
+ exit(-1);
+ }
+ gridWidth = atoi(*argv);
+ ++argv;
+ if (argv >= stop) {
+ SkDebugf("Missing height for --bbh grid\n");
+ usage(argv0);
+ exit(-1);
+ }
+ gridHeight = atoi(*argv);
+ } else {
+ SkDebugf("%s is not a valid value for --bbhType\n", *argv);
+ usage(argv0);
+ exit(-1);;
+ }
+ } else if (0 == strcmp(*argv, "--viewport")) {
+ ++argv;
+ if (argv >= stop) {
+ SkDebugf("Missing width for --viewport\n");
+ usage(argv0);
+ exit(-1);
+ }
+ viewport.fWidth = atoi(*argv);
+ ++argv;
+ if (argv >= stop) {
+ SkDebugf("Missing height for --viewport\n");
+ usage(argv0);
+ exit(-1);
+ }
+ viewport.fHeight = atoi(*argv);
+ } else if (0 == strcmp(*argv, "--scale")) {
+ ++argv;
+ if (argv >= stop) {
+ SkDebugf("Missing scaleFactor for --scale\n");
+ usage(argv0);
+ exit(-1);
+ }
+ scaleFactor = atof(*argv);
+ } else if (0 == strcmp(*argv, "--tiles")) {
+ ++argv;
+ if (argv >= stop) {
+ SkDebugf("Missing x for --tiles\n");
+ usage(argv0);
+ exit(-1);
+ }
+ xTilesString = *argv;
+ ++argv;
+ if (argv >= stop) {
+ SkDebugf("Missing y for --tiles\n");
+ usage(argv0);
+ exit(-1);
+ }
+ yTilesString = *argv;
+ } else if (0 == strcmp(*argv, "--pipe")) {
+ usePipe = true;
+ } else if (0 == strcmp(*argv, "--multi")) {
+ ++argv;
+ if (argv >= stop) {
+ SkSafeUnref(renderer);
+ SkDebugf("Missing arg for --multi\n");
+ usage(argv0);
+ exit(-1);
+ }
+ numThreads = atoi(*argv);
+ if (numThreads < 2) {
+ SkSafeUnref(renderer);
+ SkDebugf("Number of threads must be at least 2.\n");
+ usage(argv0);
+ exit(-1);
+ }
+ } else if (0 == strcmp(*argv, "--clone")) {
+ ++argv;
+ if (argv >= stop) {
+ SkSafeUnref(renderer);
+ SkDebugf("Missing arg for --clone\n");
+ usage(argv0);
+ exit(-1);
+ }
+ *clones = atoi(*argv);
+ if (*clones < 0) {
+ SkSafeUnref(renderer);
+ SkDebugf("Number of clones must be at least 0.\n");
+ usage(argv0);
+ exit(-1);
+ }
+ } else if (0 == strcmp(*argv, "--device")) {
+ ++argv;
+ if (argv >= stop) {
+ SkSafeUnref(renderer);
+ SkDebugf("Missing mode for --device\n");
+ usage(argv0);
+ exit(-1);
+ }
+
+ if (0 == strcmp(*argv, "bitmap")) {
+ deviceType = sk_tools::PictureRenderer::kBitmap_DeviceType;
+ }
+#if SK_SUPPORT_GPU
+ else if (0 == strcmp(*argv, "gpu")) {
+ deviceType = sk_tools::PictureRenderer::kGPU_DeviceType;
+ }
+#endif
+ else {
+ SkSafeUnref(renderer);
+ SkDebugf("%s is not a valid mode for --device\n", *argv);
+ usage(argv0);
+ exit(-1);
+ }
+
+ } else if ((0 == strcmp(*argv, "-h")) || (0 == strcmp(*argv, "--help"))) {
+ SkSafeUnref(renderer);
+ usage(argv0);
+ exit(-1);
+ } else if (0 == strcmp(*argv, "-w")) {
+ ++argv;
+ if (argv >= stop) {
+ SkDebugf("Missing output directory for -w\n");
+ usage(argv0);
+ exit(-1);
+ }
+ outputDir = SkNEW_ARGS(SkString, (*argv));
+ } else if (0 == strcmp(*argv, "--validate")) {
+ *validate = true;
+ } else if (0 == strcmp(*argv, "--writeWholeImage")) {
+ *writeWholeImage = true;
+ } else {
+ inputs->push_back(SkString(*argv));
+ }
+ }
+
+ if (numThreads > 1 && !useTiles) {
+ SkSafeUnref(renderer);
+ SkDebugf("Multithreaded drawing requires tiled rendering.\n");
+ usage(argv0);
+ exit(-1);
+ }
+
+ if (usePipe && sk_tools::PictureRenderer::kNone_BBoxHierarchyType != bbhType) {
+ SkDebugf("--pipe and --bbh cannot be used together\n");
+ usage(argv0);
+ exit(-1);
+ }
+
+ if (sk_tools::PictureRenderer::kTileGrid_BBoxHierarchyType == bbhType &&
+ !gridSupported) {
+ SkDebugf("'--bbh grid' is not compatible with specified --mode.\n");
+ usage(argv0);
+ exit(-1);
+ }
+
+ if (useTiles) {
+ SkASSERT(NULL == renderer);
+ sk_tools::TiledPictureRenderer* tiledRenderer;
+ if (isCopyMode) {
+ int x, y;
+ if (xTilesString != NULL) {
+ SkASSERT(yTilesString != NULL);
+ x = atoi(xTilesString);
+ y = atoi(yTilesString);
+ if (x <= 0 || y <= 0) {
+ SkDebugf("--tiles must be given values > 0\n");
+ usage(argv0);
+ exit(-1);
+ }
+ } else {
+ x = y = 4;
+ }
+ tiledRenderer = SkNEW_ARGS(sk_tools::CopyTilesRenderer, (x, y));
+ } else if (numThreads > 1) {
+ tiledRenderer = SkNEW_ARGS(sk_tools::MultiCorePictureRenderer, (numThreads));
+ } else {
+ tiledRenderer = SkNEW(sk_tools::TiledPictureRenderer);
+ }
+ if (isPowerOf2Mode) {
+ int minWidth = atoi(widthString);
+ if (!SkIsPow2(minWidth) || minWidth < 0) {
+ tiledRenderer->unref();
+ SkString err;
+ err.printf("-mode %s must be given a width"
+ " value that is a power of two\n", mode);
+ SkDebugf(err.c_str());
+ usage(argv0);
+ exit(-1);
+ }
+ tiledRenderer->setTileMinPowerOf2Width(minWidth);
+ } else if (sk_tools::is_percentage(widthString)) {
+ if (isCopyMode) {
+ tiledRenderer->unref();
+ SkString err;
+ err.printf("--mode %s does not support percentages.\n", mode);
+ SkDebugf(err.c_str());
+ usage(argv0);
+ exit(-1);
+ }
+ tiledRenderer->setTileWidthPercentage(atof(widthString));
+ if (!(tiledRenderer->getTileWidthPercentage() > 0)) {
+ tiledRenderer->unref();
+ SkDebugf("--mode %s must be given a width percentage > 0\n", mode);
+ usage(argv0);
+ exit(-1);
+ }
+ } else {
+ tiledRenderer->setTileWidth(atoi(widthString));
+ if (!(tiledRenderer->getTileWidth() > 0)) {
+ tiledRenderer->unref();
+ SkDebugf("--mode %s must be given a width > 0\n", mode);
+ usage(argv0);
+ exit(-1);
+ }
+ }
+
+ if (sk_tools::is_percentage(heightString)) {
+ if (isCopyMode) {
+ tiledRenderer->unref();
+ SkString err;
+ err.printf("--mode %s does not support percentages.\n", mode);
+ SkDebugf(err.c_str());
+ usage(argv0);
+ exit(-1);
+ }
+ tiledRenderer->setTileHeightPercentage(atof(heightString));
+ if (!(tiledRenderer->getTileHeightPercentage() > 0)) {
+ tiledRenderer->unref();
+ SkDebugf("--mode %s must be given a height percentage > 0\n", mode);
+ usage(argv0);
+ exit(-1);
+ }
+ } else {
+ tiledRenderer->setTileHeight(atoi(heightString));
+ if (!(tiledRenderer->getTileHeight() > 0)) {
+ tiledRenderer->unref();
+ SkDebugf("--mode %s must be given a height > 0\n", mode);
+ usage(argv0);
+ exit(-1);
+ }
+ }
+ if (numThreads > 1) {
+#if SK_SUPPORT_GPU
+ if (sk_tools::PictureRenderer::kGPU_DeviceType == deviceType) {
+ tiledRenderer->unref();
+ SkDebugf("GPU not compatible with multithreaded tiling.\n");
+ usage(argv0);
+ exit(-1);
+ }
+#endif
+ }
+ renderer = tiledRenderer;
+ if (usePipe) {
+ SkDebugf("Pipe rendering is currently not compatible with tiling.\n"
+ "Turning off pipe.\n");
+ }
+ } else if (usePipe) {
+ if (renderer != NULL) {
+ renderer->unref();
+ SkDebugf("Pipe is incompatible with other modes.\n");
+ usage(argv0);
+ exit(-1);
+ }
+ renderer = SkNEW(sk_tools::PipePictureRenderer);
+ }
+
+ if (inputs->empty()) {
+ SkSafeUnref(renderer);
+ if (NULL != outputDir) {
+ SkDELETE(outputDir);
+ }
+ usage(argv0);
+ exit(-1);
+ }
+
+ if (NULL == renderer) {
+ renderer = SkNEW(sk_tools::SimplePictureRenderer);
+ }
+
+ renderer->setBBoxHierarchyType(bbhType);
+ renderer->setGridSize(gridWidth, gridHeight);
+ renderer->setViewport(viewport);
+ renderer->setScaleFactor(scaleFactor);
+ renderer->setDeviceType(deviceType);
+}
+
+int tool_main(int argc, char** argv);
+int tool_main(int argc, char** argv) {
+ SkAutoGraphics ag;
+ SkTArray<SkString> inputs;
+ sk_tools::PictureRenderer* renderer = NULL;
+ SkString* outputDir = NULL;
+ bool validate = false;
+ bool writeWholeImage = false;
+ int clones = 0;
+ parse_commandline(argc, argv, &inputs, renderer, outputDir,
+ &validate, &writeWholeImage, &clones);
+ SkASSERT(renderer);
+
+ int failures = 0;
+ for (int i = 0; i < inputs.count(); i ++) {
+ failures += process_input(inputs[i], outputDir, *renderer,
+ validate, writeWholeImage, clones);
+ }
+ if (failures != 0) {
+ SkDebugf("Failed to render %i pictures.\n", failures);
+ return 1;
+ }
+#if SK_SUPPORT_GPU
+#if GR_CACHE_STATS
+ if (renderer->isUsingGpuDevice()) {
+ GrContext* ctx = renderer->getGrContext();
+
+ ctx->printCacheStats();
+ }
+#endif
+#endif
+ if (NULL != outputDir) {
+ SkDELETE(outputDir);
+ }
+ SkDELETE(renderer);
+ return 0;
+}
+
+#if !defined SK_BUILD_FOR_IOS
+int main(int argc, char * const argv[]) {
+ return tool_main(argc, (char**) argv);
+}
+#endif
diff --git a/tools/skdiff.cpp b/tools/skdiff.cpp
new file mode 100644
index 0000000000..a1783a45de
--- /dev/null
+++ b/tools/skdiff.cpp
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "skdiff.h"
+#include "SkBitmap.h"
+#include "SkColor.h"
+#include "SkColorPriv.h"
+#include "SkTypes.h"
+
+/*static*/ char const * const DiffRecord::ResultNames[DiffRecord::kResultCount] = {
+ "EqualBits",
+ "EqualPixels",
+ "DifferentPixels",
+ "DifferentSizes",
+ "CouldNotCompare",
+ "Unknown",
+};
+
+DiffRecord::Result DiffRecord::getResultByName(const char *name) {
+ for (int result = 0; result < DiffRecord::kResultCount; ++result) {
+ if (0 == strcmp(DiffRecord::ResultNames[result], name)) {
+ return static_cast<DiffRecord::Result>(result);
+ }
+ }
+ return DiffRecord::kResultCount;
+}
+
+static char const * const ResultDescriptions[DiffRecord::kResultCount] = {
+ "contain exactly the same bits",
+ "contain the same pixel values, but not the same bits",
+ "have identical dimensions but some differing pixels",
+ "have differing dimensions",
+ "could not be compared",
+ "not compared yet",
+};
+
+const char* DiffRecord::getResultDescription(DiffRecord::Result result) {
+ return ResultDescriptions[result];
+}
+
+/*static*/ char const * const DiffResource::StatusNames[DiffResource::kStatusCount] = {
+ "Decoded",
+ "CouldNotDecode",
+
+ "Read",
+ "CouldNotRead",
+
+ "Exists",
+ "DoesNotExist",
+
+ "Specified",
+ "Unspecified",
+
+ "Unknown",
+};
+
+DiffResource::Status DiffResource::getStatusByName(const char *name) {
+ for (int status = 0; status < DiffResource::kStatusCount; ++status) {
+ if (0 == strcmp(DiffResource::StatusNames[status], name)) {
+ return static_cast<DiffResource::Status>(status);
+ }
+ }
+ return DiffResource::kStatusCount;
+}
+
+static char const * const StatusDescriptions[DiffResource::kStatusCount] = {
+ "decoded",
+ "could not be decoded",
+
+ "read",
+ "could not be read",
+
+ "found",
+ "not found",
+
+ "specified",
+ "unspecified",
+
+ "unknown",
+};
+
+const char* DiffResource::getStatusDescription(DiffResource::Status status) {
+ return StatusDescriptions[status];
+}
+
+bool DiffResource::isStatusFailed(DiffResource::Status status) {
+ return DiffResource::kCouldNotDecode_Status == status ||
+ DiffResource::kCouldNotRead_Status == status ||
+ DiffResource::kDoesNotExist_Status == status ||
+ DiffResource::kUnspecified_Status == status ||
+ DiffResource::kUnknown_Status == status;
+}
+
+bool DiffResource::getMatchingStatuses(char* selector, bool statuses[kStatusCount]) {
+ if (!strcmp(selector, "any")) {
+ for (int statusIndex = 0; statusIndex < kStatusCount; ++statusIndex) {
+ statuses[statusIndex] = true;
+ }
+ return true;
+ }
+
+ for (int statusIndex = 0; statusIndex < kStatusCount; ++statusIndex) {
+ statuses[statusIndex] = false;
+ }
+
+ static const char kDelimiterChar = ',';
+ bool understood = true;
+ while (true) {
+ char* delimiterPtr = strchr(selector, kDelimiterChar);
+
+ if (delimiterPtr) {
+ *delimiterPtr = '\0';
+ }
+
+ if (!strcmp(selector, "failed")) {
+ for (int statusIndex = 0; statusIndex < kStatusCount; ++statusIndex) {
+ Status status = static_cast<Status>(statusIndex);
+ statuses[statusIndex] |= isStatusFailed(status);
+ }
+ } else {
+ Status status = getStatusByName(selector);
+ if (status == kStatusCount) {
+ understood = false;
+ } else {
+ statuses[status] = true;
+ }
+ }
+
+ if (!delimiterPtr) {
+ break;
+ }
+
+ *delimiterPtr = kDelimiterChar;
+ selector = delimiterPtr + 1;
+ }
+ return understood;
+}
+
+static inline bool colors_match_thresholded(SkPMColor c0, SkPMColor c1, const int threshold) {
+ int da = SkGetPackedA32(c0) - SkGetPackedA32(c1);
+ int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
+ int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
+ int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
+
+ return ((SkAbs32(da) <= threshold) &&
+ (SkAbs32(dr) <= threshold) &&
+ (SkAbs32(dg) <= threshold) &&
+ (SkAbs32(db) <= threshold));
+}
+
+const SkPMColor PMCOLOR_WHITE = SkPreMultiplyColor(SK_ColorWHITE);
+const SkPMColor PMCOLOR_BLACK = SkPreMultiplyColor(SK_ColorBLACK);
+
+void compute_diff(DiffRecord* dr, DiffMetricProc diffFunction, const int colorThreshold) {
+ const int w = dr->fComparison.fBitmap.width();
+ const int h = dr->fComparison.fBitmap.height();
+ if (w != dr->fBase.fBitmap.width() || h != dr->fBase.fBitmap.height()) {
+ dr->fResult = DiffRecord::kDifferentSizes_Result;
+ return;
+ }
+
+ SkAutoLockPixels alpDiff(dr->fDifference.fBitmap);
+ SkAutoLockPixels alpWhite(dr->fWhite.fBitmap);
+ int mismatchedPixels = 0;
+ int totalMismatchR = 0;
+ int totalMismatchG = 0;
+ int totalMismatchB = 0;
+
+ // Accumulate fractionally different pixels, then divide out
+ // # of pixels at the end.
+ dr->fWeightedFraction = 0;
+ for (int y = 0; y < h; y++) {
+ for (int x = 0; x < w; x++) {
+ SkPMColor c0 = *dr->fBase.fBitmap.getAddr32(x, y);
+ SkPMColor c1 = *dr->fComparison.fBitmap.getAddr32(x, y);
+ SkPMColor trueDifference = compute_diff_pmcolor(c0, c1);
+ SkPMColor outputDifference = diffFunction(c0, c1);
+ uint32_t thisR = SkGetPackedR32(trueDifference);
+ uint32_t thisG = SkGetPackedG32(trueDifference);
+ uint32_t thisB = SkGetPackedB32(trueDifference);
+ totalMismatchR += thisR;
+ totalMismatchG += thisG;
+ totalMismatchB += thisB;
+ // In HSV, value is defined as max RGB component.
+ int value = MAX3(thisR, thisG, thisB);
+ dr->fWeightedFraction += ((float) value) / 255;
+ if (thisR > dr->fMaxMismatchR) {
+ dr->fMaxMismatchR = thisR;
+ }
+ if (thisG > dr->fMaxMismatchG) {
+ dr->fMaxMismatchG = thisG;
+ }
+ if (thisB > dr->fMaxMismatchB) {
+ dr->fMaxMismatchB = thisB;
+ }
+ if (!colors_match_thresholded(c0, c1, colorThreshold)) {
+ mismatchedPixels++;
+ *dr->fDifference.fBitmap.getAddr32(x, y) = outputDifference;
+ *dr->fWhite.fBitmap.getAddr32(x, y) = PMCOLOR_WHITE;
+ } else {
+ *dr->fDifference.fBitmap.getAddr32(x, y) = 0;
+ *dr->fWhite.fBitmap.getAddr32(x, y) = PMCOLOR_BLACK;
+ }
+ }
+ }
+ if (0 == mismatchedPixels) {
+ dr->fResult = DiffRecord::kEqualPixels_Result;
+ return;
+ }
+ dr->fResult = DiffRecord::kDifferentPixels_Result;
+ int pixelCount = w * h;
+ dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount;
+ dr->fWeightedFraction /= pixelCount;
+ dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
+ dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
+ dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
+}
diff --git a/tools/skdiff.h b/tools/skdiff.h
new file mode 100644
index 0000000000..b9e69ced85
--- /dev/null
+++ b/tools/skdiff.h
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef skdiff_DEFINED
+#define skdiff_DEFINED
+
+#include "SkBitmap.h"
+#include "SkColor.h"
+#include "SkColorPriv.h"
+#include "SkString.h"
+#include "SkTDArray.h"
+
+#if SK_BUILD_FOR_WIN32
+ #define PATH_DIV_STR "\\"
+ #define PATH_DIV_CHAR '\\'
+#else
+ #define PATH_DIV_STR "/"
+ #define PATH_DIV_CHAR '/'
+#endif
+
+#define MAX2(a,b) (((b) < (a)) ? (a) : (b))
+#define MAX3(a,b,c) (((b) < (a)) ? MAX2((a), (c)) : MAX2((b), (c)))
+
+
+struct DiffResource {
+ enum Status {
+ /** The resource was specified, exists, read, and decoded. */
+ kDecoded_Status,
+ /** The resource was specified, exists, read, but could not be decoded. */
+ kCouldNotDecode_Status,
+
+ /** The resource was specified, exists, and read. */
+ kRead_Status,
+ /** The resource was specified, exists, but could not be read. */
+ kCouldNotRead_Status,
+
+ /** The resource was specified and exists. */
+ kExists_Status,
+ /** The resource was specified, but does not exist. */
+ kDoesNotExist_Status,
+
+ /** The resource was specified. */
+ kSpecified_Status,
+ /** The resource was not specified. */
+ kUnspecified_Status,
+
+ /** Nothing is yet known about the resource. */
+ kUnknown_Status,
+
+ /** NOT A VALID VALUE -- used to set up arrays and to represent an unknown value. */
+ kStatusCount
+ };
+ static char const * const StatusNames[DiffResource::kStatusCount];
+
+ /** Returns the Status with this name.
+ * If there is no Status with this name, returns kStatusCount.
+ */
+ static Status getStatusByName(const char *name);
+
+ /** Returns a text description of the given Status type. */
+ static const char *getStatusDescription(Status status);
+
+ /** Returns true if the Status indicates some kind of failure. */
+ static bool isStatusFailed(Status status);
+
+ /** Sets statuses[i] if it is implied by selector, unsets it if not.
+ * Selector may be a comma delimited list of status names, "any", or "failed".
+ * Returns true if the selector was entirely understood, false otherwise.
+ */
+ static bool getMatchingStatuses(char* selector, bool statuses[kStatusCount]);
+
+ DiffResource() : fFilename(), fFullPath(), fBitmap(), fStatus(kUnknown_Status) { };
+
+ /** If isEmpty() indicates no filename available. */
+ SkString fFilename;
+ /** If isEmpty() indicates no path available. */
+ SkString fFullPath;
+ /** If empty() indicates the bitmap could not be created. */
+ SkBitmap fBitmap;
+ Status fStatus;
+};
+
+struct DiffRecord {
+
+ // Result of comparison for each pair of files.
+ // Listed from "better" to "worse", for sorting of results.
+ enum Result {
+ kEqualBits_Result,
+ kEqualPixels_Result,
+ kDifferentPixels_Result,
+ kDifferentSizes_Result,
+ kCouldNotCompare_Result,
+ kUnknown_Result,
+
+ kResultCount // NOT A VALID VALUE--used to set up arrays. Must be last.
+ };
+ static char const * const ResultNames[DiffRecord::kResultCount];
+
+ /** Returns the Result with this name.
+ * If there is no Result with this name, returns kResultCount.
+ */
+ static Result getResultByName(const char *name);
+
+ /** Returns a text description of the given Result type. */
+ static const char *getResultDescription(Result result);
+
+ DiffRecord()
+ : fBase()
+ , fComparison()
+ , fDifference()
+ , fWhite()
+ , fFractionDifference(0)
+ , fWeightedFraction(0)
+ , fAverageMismatchR(0)
+ , fAverageMismatchG(0)
+ , fAverageMismatchB(0)
+ , fMaxMismatchR(0)
+ , fMaxMismatchG(0)
+ , fMaxMismatchB(0)
+ , fResult(kUnknown_Result) {
+ };
+
+ DiffResource fBase;
+ DiffResource fComparison;
+ DiffResource fDifference;
+ DiffResource fWhite;
+
+ /// Arbitrary floating-point metric to be used to sort images from most
+ /// to least different from baseline; values of 0 will be omitted from the
+ /// summary webpage.
+ float fFractionDifference;
+ float fWeightedFraction;
+
+ float fAverageMismatchR;
+ float fAverageMismatchG;
+ float fAverageMismatchB;
+
+ uint32_t fMaxMismatchR;
+ uint32_t fMaxMismatchG;
+ uint32_t fMaxMismatchB;
+
+ /// Which category of diff result.
+ Result fResult;
+};
+
+typedef SkTDArray<DiffRecord*> RecordArray;
+
+/// A wrapper for any sortProc (comparison routine) which applies a first-order
+/// sort beforehand, and a tiebreaker if the sortProc returns 0.
+template<typename T> static int compare(const void* untyped_lhs, const void* untyped_rhs) {
+ const DiffRecord* lhs = *reinterpret_cast<DiffRecord* const *>(untyped_lhs);
+ const DiffRecord* rhs = *reinterpret_cast<DiffRecord* const *>(untyped_rhs);
+
+ // First-order sort... these comparisons should be applied before comparing
+ // pixel values, no matter what.
+ if (lhs->fResult != rhs->fResult) {
+ return (lhs->fResult < rhs->fResult) ? 1 : -1;
+ }
+
+ // Passed first-order sort, so call the pixel comparison routine.
+ int result = T::comparePixels(lhs, rhs);
+ if (result != 0) {
+ return result;
+ }
+
+ // Tiebreaker... if we got to this point, we don't really care
+ // which order they are sorted in, but let's at least be consistent.
+ return strcmp(lhs->fBase.fFilename.c_str(), rhs->fBase.fFilename.c_str());
+}
+
+/// Comparison routine for qsort; sorts by fFractionDifference
+/// from largest to smallest.
+class CompareDiffMetrics {
+public:
+ static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
+ if (lhs->fFractionDifference < rhs->fFractionDifference) {
+ return 1;
+ }
+ if (rhs->fFractionDifference < lhs->fFractionDifference) {
+ return -1;
+ }
+ return 0;
+ }
+};
+
+class CompareDiffWeighted {
+public:
+ static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
+ if (lhs->fWeightedFraction < rhs->fWeightedFraction) {
+ return 1;
+ }
+ if (lhs->fWeightedFraction > rhs->fWeightedFraction) {
+ return -1;
+ }
+ return 0;
+ }
+};
+
+/// Comparison routine for qsort; sorts by max(fAverageMismatch{RGB})
+/// from largest to smallest.
+class CompareDiffMeanMismatches {
+public:
+ static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
+ float leftValue = MAX3(lhs->fAverageMismatchR,
+ lhs->fAverageMismatchG,
+ lhs->fAverageMismatchB);
+ float rightValue = MAX3(rhs->fAverageMismatchR,
+ rhs->fAverageMismatchG,
+ rhs->fAverageMismatchB);
+ if (leftValue < rightValue) {
+ return 1;
+ }
+ if (rightValue < leftValue) {
+ return -1;
+ }
+ return 0;
+ }
+};
+
+/// Comparison routine for qsort; sorts by max(fMaxMismatch{RGB})
+/// from largest to smallest.
+class CompareDiffMaxMismatches {
+public:
+ static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
+ uint32_t leftValue = MAX3(lhs->fMaxMismatchR,
+ lhs->fMaxMismatchG,
+ lhs->fMaxMismatchB);
+ uint32_t rightValue = MAX3(rhs->fMaxMismatchR,
+ rhs->fMaxMismatchG,
+ rhs->fMaxMismatchB);
+ if (leftValue < rightValue) {
+ return 1;
+ }
+ if (rightValue < leftValue) {
+ return -1;
+ }
+
+ return CompareDiffMeanMismatches::comparePixels(lhs, rhs);
+ }
+};
+
+
+/// Parameterized routine to compute the color of a pixel in a difference image.
+typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor);
+
+// from gm
+static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
+ int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
+ int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
+ int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
+
+ return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
+}
+
+/** When finished, dr->fResult should have some value other than kUnknown_Result.
+ * Expects dr->fWhite.fBitmap and dr->fDifference.fBitmap to have the same bounds as
+ * dr->fBase.fBitmap and have a valid pixelref.
+ */
+void compute_diff(DiffRecord* dr, DiffMetricProc diffFunction, const int colorThreshold);
+
+#endif
diff --git a/tools/skdiff_html.cpp b/tools/skdiff_html.cpp
new file mode 100644
index 0000000000..85d8777712
--- /dev/null
+++ b/tools/skdiff_html.cpp
@@ -0,0 +1,300 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "skdiff.h"
+#include "skdiff_html.h"
+#include "SkStream.h"
+#include "SkTime.h"
+
+/// Make layout more consistent by scaling image to 240 height, 360 width,
+/// or natural size, whichever is smallest.
+static int compute_image_height(int height, int width) {
+ int retval = 240;
+ if (height < retval) {
+ retval = height;
+ }
+ float scale = (float) retval / height;
+ if (width * scale > 360) {
+ scale = (float) 360 / width;
+ retval = static_cast<int>(height * scale);
+ }
+ return retval;
+}
+
+static void print_table_header(SkFILEWStream* stream,
+ const int matchCount,
+ const int colorThreshold,
+ const RecordArray& differences,
+ const SkString &baseDir,
+ const SkString &comparisonDir,
+ bool doOutputDate = false) {
+ stream->writeText("<table>\n");
+ stream->writeText("<tr><th>");
+ stream->writeText("select image</th>\n<th>");
+ if (doOutputDate) {
+ SkTime::DateTime dt;
+ SkTime::GetDateTime(&dt);
+ stream->writeText("SkDiff run at ");
+ stream->writeDecAsText(dt.fHour);
+ stream->writeText(":");
+ if (dt.fMinute < 10) {
+ stream->writeText("0");
+ }
+ stream->writeDecAsText(dt.fMinute);
+ stream->writeText(":");
+ if (dt.fSecond < 10) {
+ stream->writeText("0");
+ }
+ stream->writeDecAsText(dt.fSecond);
+ stream->writeText("<br>");
+ }
+ stream->writeDecAsText(matchCount);
+ stream->writeText(" of ");
+ stream->writeDecAsText(differences.count());
+ stream->writeText(" diffs matched ");
+ if (colorThreshold == 0) {
+ stream->writeText("exactly");
+ } else {
+ stream->writeText("within ");
+ stream->writeDecAsText(colorThreshold);
+ stream->writeText(" color units per component");
+ }
+ stream->writeText(".<br>");
+ stream->writeText("</th>\n<th>");
+ stream->writeText("every different pixel shown in white");
+ stream->writeText("</th>\n<th>");
+ stream->writeText("color difference at each pixel");
+ stream->writeText("</th>\n<th>baseDir: ");
+ stream->writeText(baseDir.c_str());
+ stream->writeText("</th>\n<th>comparisonDir: ");
+ stream->writeText(comparisonDir.c_str());
+ stream->writeText("</th>\n");
+ stream->writeText("</tr>\n");
+}
+
+static void print_pixel_count(SkFILEWStream* stream, const DiffRecord& diff) {
+ stream->writeText("<br>(");
+ stream->writeDecAsText(static_cast<int>(diff.fFractionDifference *
+ diff.fBase.fBitmap.width() *
+ diff.fBase.fBitmap.height()));
+ stream->writeText(" pixels)");
+/*
+ stream->writeDecAsText(diff.fWeightedFraction *
+ diff.fBaseWidth *
+ diff.fBaseHeight);
+ stream->writeText(" weighted pixels)");
+*/
+}
+
+static void print_checkbox_cell(SkFILEWStream* stream, const DiffRecord& diff) {
+ stream->writeText("<td><input type=\"checkbox\" name=\"");
+ stream->writeText(diff.fBase.fFilename.c_str());
+ stream->writeText("\" checked=\"yes\"></td>");
+}
+
+static void print_label_cell(SkFILEWStream* stream, const DiffRecord& diff) {
+ char metricBuf [20];
+
+ stream->writeText("<td><b>");
+ stream->writeText(diff.fBase.fFilename.c_str());
+ stream->writeText("</b><br>");
+ switch (diff.fResult) {
+ case DiffRecord::kEqualBits_Result:
+ SkDEBUGFAIL("should not encounter DiffRecord with kEqualBits here");
+ return;
+ case DiffRecord::kEqualPixels_Result:
+ SkDEBUGFAIL("should not encounter DiffRecord with kEqualPixels here");
+ return;
+ case DiffRecord::kDifferentSizes_Result:
+ stream->writeText("Image sizes differ</td>");
+ return;
+ case DiffRecord::kDifferentPixels_Result:
+ sprintf(metricBuf, "%.4f%%", 100 * diff.fFractionDifference);
+ stream->writeText(metricBuf);
+ stream->writeText(" of pixels differ");
+ stream->writeText("\n (");
+ sprintf(metricBuf, "%.4f%%", 100 * diff.fWeightedFraction);
+ stream->writeText(metricBuf);
+ stream->writeText(" weighted)");
+ // Write the actual number of pixels that differ if it's < 1%
+ if (diff.fFractionDifference < 0.01) {
+ print_pixel_count(stream, diff);
+ }
+ stream->writeText("<br>Average color mismatch ");
+ stream->writeDecAsText(static_cast<int>(MAX3(diff.fAverageMismatchR,
+ diff.fAverageMismatchG,
+ diff.fAverageMismatchB)));
+ stream->writeText("<br>Max color mismatch ");
+ stream->writeDecAsText(MAX3(diff.fMaxMismatchR,
+ diff.fMaxMismatchG,
+ diff.fMaxMismatchB));
+ stream->writeText("</td>");
+ break;
+ case DiffRecord::kCouldNotCompare_Result:
+ stream->writeText("Could not compare.<br>base: ");
+ stream->writeText(DiffResource::getStatusDescription(diff.fBase.fStatus));
+ stream->writeText("<br>comparison: ");
+ stream->writeText(DiffResource::getStatusDescription(diff.fComparison.fStatus));
+ stream->writeText("</td>");
+ return;
+ default:
+ SkDEBUGFAIL("encountered DiffRecord with unknown result type");
+ return;
+ }
+}
+
+static void print_image_cell(SkFILEWStream* stream, const SkString& path, int height) {
+ stream->writeText("<td><a href=\"");
+ stream->writeText(path.c_str());
+ stream->writeText("\"><img src=\"");
+ stream->writeText(path.c_str());
+ stream->writeText("\" height=\"");
+ stream->writeDecAsText(height);
+ stream->writeText("px\"></a></td>");
+}
+
+static void print_link_cell(SkFILEWStream* stream, const SkString& path, const char* text) {
+ stream->writeText("<td><a href=\"");
+ stream->writeText(path.c_str());
+ stream->writeText("\">");
+ stream->writeText(text);
+ stream->writeText("</a></td>");
+}
+
+static void print_diff_resource_cell(SkFILEWStream* stream, DiffResource& resource,
+ const SkString& relativePath, bool local) {
+ if (resource.fBitmap.empty()) {
+ if (DiffResource::kCouldNotDecode_Status == resource.fStatus) {
+ if (local && !resource.fFilename.isEmpty()) {
+ print_link_cell(stream, resource.fFilename, "N/A");
+ return;
+ }
+ if (!resource.fFullPath.isEmpty()) {
+ if (!resource.fFullPath.startsWith(PATH_DIV_STR)) {
+ resource.fFullPath.prepend(relativePath);
+ }
+ print_link_cell(stream, resource.fFullPath, "N/A");
+ return;
+ }
+ }
+ stream->writeText("<td>N/A</td>");
+ return;
+ }
+
+ int height = compute_image_height(resource.fBitmap.height(), resource.fBitmap.width());
+ if (local) {
+ print_image_cell(stream, resource.fFilename, height);
+ return;
+ }
+ if (!resource.fFullPath.startsWith(PATH_DIV_STR)) {
+ resource.fFullPath.prepend(relativePath);
+ }
+ print_image_cell(stream, resource.fFullPath, height);
+}
+
+static void print_diff_row(SkFILEWStream* stream, DiffRecord& diff, const SkString& relativePath) {
+ stream->writeText("<tr>\n");
+ print_checkbox_cell(stream, diff);
+ print_label_cell(stream, diff);
+ print_diff_resource_cell(stream, diff.fWhite, relativePath, true);
+ print_diff_resource_cell(stream, diff.fDifference, relativePath, true);
+ print_diff_resource_cell(stream, diff.fBase, relativePath, false);
+ print_diff_resource_cell(stream, diff.fComparison, relativePath, false);
+ stream->writeText("</tr>\n");
+ stream->flush();
+}
+
+void print_diff_page(const int matchCount,
+ const int colorThreshold,
+ const RecordArray& differences,
+ const SkString& baseDir,
+ const SkString& comparisonDir,
+ const SkString& outputDir) {
+
+ SkASSERT(!baseDir.isEmpty());
+ SkASSERT(!comparisonDir.isEmpty());
+ SkASSERT(!outputDir.isEmpty());
+
+ SkString outputPath(outputDir);
+ outputPath.append("index.html");
+ //SkFILEWStream outputStream ("index.html");
+ SkFILEWStream outputStream(outputPath.c_str());
+
+ // Need to convert paths from relative-to-cwd to relative-to-outputDir
+ // FIXME this doesn't work if there are '..' inside the outputDir
+
+ bool isPathAbsolute = false;
+ // On Windows or Linux, a path starting with PATH_DIV_CHAR is absolute.
+ if (outputDir.size() > 0 && PATH_DIV_CHAR == outputDir[0]) {
+ isPathAbsolute = true;
+ }
+#ifdef SK_BUILD_FOR_WIN32
+ // On Windows, absolute paths can also start with "x:", where x is any
+ // drive letter.
+ if (outputDir.size() > 1 && ':' == outputDir[1]) {
+ isPathAbsolute = true;
+ }
+#endif
+
+ SkString relativePath;
+ if (!isPathAbsolute) {
+ unsigned int ui;
+ for (ui = 0; ui < outputDir.size(); ui++) {
+ if (outputDir[ui] == PATH_DIV_CHAR) {
+ relativePath.append(".." PATH_DIV_STR);
+ }
+ }
+ }
+
+ outputStream.writeText(
+ "<html>\n<head>\n"
+ "<script src=\"https://ajax.googleapis.com/ajax/"
+ "libs/jquery/1.7.2/jquery.min.js\"></script>\n"
+ "<script type=\"text/javascript\">\n"
+ "function generateCheckedList() {\n"
+ "var boxes = $(\":checkbox:checked\");\n"
+ "var fileCmdLineString = '';\n"
+ "var fileMultiLineString = '';\n"
+ "for (var i = 0; i < boxes.length; i++) {\n"
+ "fileMultiLineString += boxes[i].name + '<br>';\n"
+ "fileCmdLineString += boxes[i].name + '&nbsp;';\n"
+ "}\n"
+ "$(\"#checkedList\").html(fileCmdLineString + "
+ "'<br><br>' + fileMultiLineString);\n"
+ "}\n"
+ "</script>\n</head>\n<body>\n");
+ print_table_header(&outputStream, matchCount, colorThreshold, differences,
+ baseDir, comparisonDir);
+ int i;
+ for (i = 0; i < differences.count(); i++) {
+ DiffRecord* diff = differences[i];
+
+ switch (diff->fResult) {
+ // Cases in which there is no diff to report.
+ case DiffRecord::kEqualBits_Result:
+ case DiffRecord::kEqualPixels_Result:
+ continue;
+ // Cases in which we want a detailed pixel diff.
+ case DiffRecord::kDifferentPixels_Result:
+ case DiffRecord::kDifferentSizes_Result:
+ case DiffRecord::kCouldNotCompare_Result:
+ print_diff_row(&outputStream, *diff, relativePath);
+ continue;
+ default:
+ SkDEBUGFAIL("encountered DiffRecord with unknown result type");
+ continue;
+ }
+ }
+ outputStream.writeText(
+ "</table>\n"
+ "<input type=\"button\" "
+ "onclick=\"generateCheckedList()\" "
+ "value=\"Create Rebaseline List\">\n"
+ "<div id=\"checkedList\"></div>\n"
+ "</body>\n</html>\n");
+ outputStream.flush();
+}
diff --git a/tools/skdiff_html.h b/tools/skdiff_html.h
new file mode 100644
index 0000000000..eefbebf2fd
--- /dev/null
+++ b/tools/skdiff_html.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef skdiff_html_DEFINED
+#define skdiff_html_DEFINED
+
+#include "skdiff.h"
+class SkString;
+
+void print_diff_page(const int matchCount,
+ const int colorThreshold,
+ const RecordArray& differences,
+ const SkString& baseDir,
+ const SkString& comparisonDir,
+ const SkString& outputDir);
+
+#endif
diff --git a/tools/skdiff_image.cpp b/tools/skdiff_image.cpp
new file mode 100644
index 0000000000..14875874c2
--- /dev/null
+++ b/tools/skdiff_image.cpp
@@ -0,0 +1,375 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "skdiff.h"
+#include "skdiff_utils.h"
+#include "SkBitmap.h"
+#include "SkData.h"
+#include "SkImageDecoder.h"
+#include "SkImageEncoder.h"
+#include "SkOSFile.h"
+#include "SkTDArray.h"
+#include "SkTemplates.h"
+#include "SkTypes.h"
+
+/// If outputDir.isEmpty(), don't write out diff files.
+static void create_diff_images (DiffMetricProc dmp,
+ const int colorThreshold,
+ const SkString& baseFile,
+ const SkString& comparisonFile,
+ const SkString& outputDir,
+ const SkString& outputFilename,
+ DiffRecord* drp) {
+ SkASSERT(!baseFile.isEmpty());
+ SkASSERT(!comparisonFile.isEmpty());
+
+ drp->fBase.fFilename = baseFile;
+ drp->fBase.fFullPath = baseFile;
+ drp->fBase.fStatus = DiffResource::kSpecified_Status;
+
+ drp->fComparison.fFilename = comparisonFile;
+ drp->fComparison.fFullPath = comparisonFile;
+ drp->fComparison.fStatus = DiffResource::kSpecified_Status;
+
+ SkAutoDataUnref baseFileBits(read_file(drp->fBase.fFullPath.c_str()));
+ if (NULL != baseFileBits) {
+ drp->fBase.fStatus = DiffResource::kRead_Status;
+ }
+ SkAutoDataUnref comparisonFileBits(read_file(drp->fComparison.fFullPath.c_str()));
+ if (NULL != comparisonFileBits) {
+ drp->fComparison.fStatus = DiffResource::kRead_Status;
+ }
+ if (NULL == baseFileBits || NULL == comparisonFileBits) {
+ if (NULL == baseFileBits) {
+ drp->fBase.fStatus = DiffResource::kCouldNotRead_Status;
+ }
+ if (NULL == comparisonFileBits) {
+ drp->fComparison.fStatus = DiffResource::kCouldNotRead_Status;
+ }
+ drp->fResult = DiffRecord::kCouldNotCompare_Result;
+ return;
+ }
+
+ if (are_buffers_equal(baseFileBits, comparisonFileBits)) {
+ drp->fResult = DiffRecord::kEqualBits_Result;
+ return;
+ }
+
+ get_bitmap(baseFileBits, drp->fBase, SkImageDecoder::kDecodePixels_Mode);
+ get_bitmap(comparisonFileBits, drp->fComparison, SkImageDecoder::kDecodePixels_Mode);
+ if (DiffResource::kDecoded_Status != drp->fBase.fStatus ||
+ DiffResource::kDecoded_Status != drp->fComparison.fStatus)
+ {
+ drp->fResult = DiffRecord::kCouldNotCompare_Result;
+ return;
+ }
+
+ create_and_write_diff_image(drp, dmp, colorThreshold, outputDir, outputFilename);
+ //TODO: copy fBase.fFilename and fComparison.fFilename to outputDir
+ // svn and git often present tmp files to diff tools which are promptly deleted
+
+ //TODO: serialize drp to outputDir
+ // write a tool to deserialize them and call print_diff_page
+
+ SkASSERT(DiffRecord::kUnknown_Result != drp->fResult);
+}
+
+static void usage (char * argv0) {
+ SkDebugf("Skia image diff tool\n");
+ SkDebugf("\n"
+"Usage: \n"
+" %s <baseFile> <comparisonFile>\n" , argv0);
+ SkDebugf(
+"\nArguments:"
+"\n --failonresult <result>: After comparing all file pairs, exit with nonzero"
+"\n return code (number of file pairs yielding this"
+"\n result) if any file pairs yielded this result."
+"\n This flag may be repeated, in which case the"
+"\n return code will be the number of fail pairs"
+"\n yielding ANY of these results."
+"\n --failonstatus <baseStatus> <comparisonStatus>: exit with nonzero return"
+"\n code if any file pairs yeilded this status."
+"\n --help: display this info"
+"\n --listfilenames: list all filenames for each result type in stdout"
+"\n --nodiffs: don't write out image diffs, just generate report on stdout"
+"\n --outputdir: directory to write difference images"
+"\n --threshold <n>: only report differences > n (per color channel) [default 0]"
+"\n -u: ignored. Recognized for compatibility with svn diff."
+"\n -L: first occurrence label for base, second occurrence label for comparison."
+"\n Labels must be of the form \"<filename>(\t<specifier>)?\"."
+"\n The base <filename> will be used to create files in outputdir."
+"\n"
+"\n baseFile: baseline image file."
+"\n comparisonFile: comparison image file"
+"\n"
+"\nIf no sort is specified, it will sort by fraction of pixels mismatching."
+"\n");
+}
+
+const int kNoError = 0;
+const int kGenericError = -1;
+
+int tool_main(int argc, char** argv);
+int tool_main(int argc, char** argv) {
+ DiffMetricProc diffProc = compute_diff_pmcolor;
+
+ // Maximum error tolerated in any one color channel in any one pixel before
+ // a difference is reported.
+ int colorThreshold = 0;
+ SkString baseFile;
+ SkString baseLabel;
+ SkString comparisonFile;
+ SkString comparisonLabel;
+ SkString outputDir;
+
+ bool listFilenames = false;
+
+ bool failOnResultType[DiffRecord::kResultCount];
+ for (int i = 0; i < DiffRecord::kResultCount; i++) {
+ failOnResultType[i] = false;
+ }
+
+ bool failOnStatusType[DiffResource::kStatusCount][DiffResource::kStatusCount];
+ for (int base = 0; base < DiffResource::kStatusCount; ++base) {
+ for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
+ failOnStatusType[base][comparison] = false;
+ }
+ }
+
+ int i;
+ int numUnflaggedArguments = 0;
+ int numLabelArguments = 0;
+ for (i = 1; i < argc; i++) {
+ if (!strcmp(argv[i], "--failonresult")) {
+ if (argc == ++i) {
+ SkDebugf("failonresult expects one argument.\n");
+ continue;
+ }
+ DiffRecord::Result type = DiffRecord::getResultByName(argv[i]);
+ if (type != DiffRecord::kResultCount) {
+ failOnResultType[type] = true;
+ } else {
+ SkDebugf("ignoring unrecognized result <%s>\n", argv[i]);
+ }
+ continue;
+ }
+ if (!strcmp(argv[i], "--failonstatus")) {
+ if (argc == ++i) {
+ SkDebugf("failonstatus missing base status.\n");
+ continue;
+ }
+ bool baseStatuses[DiffResource::kStatusCount];
+ if (!DiffResource::getMatchingStatuses(argv[i], baseStatuses)) {
+ SkDebugf("unrecognized base status <%s>\n", argv[i]);
+ }
+
+ if (argc == ++i) {
+ SkDebugf("failonstatus missing comparison status.\n");
+ continue;
+ }
+ bool comparisonStatuses[DiffResource::kStatusCount];
+ if (!DiffResource::getMatchingStatuses(argv[i], comparisonStatuses)) {
+ SkDebugf("unrecognized comarison status <%s>\n", argv[i]);
+ }
+
+ for (int base = 0; base < DiffResource::kStatusCount; ++base) {
+ for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
+ failOnStatusType[base][comparison] |=
+ baseStatuses[base] && comparisonStatuses[comparison];
+ }
+ }
+ continue;
+ }
+ if (!strcmp(argv[i], "--help")) {
+ usage(argv[0]);
+ return kNoError;
+ }
+ if (!strcmp(argv[i], "--listfilenames")) {
+ listFilenames = true;
+ continue;
+ }
+ if (!strcmp(argv[i], "--outputdir")) {
+ if (argc == ++i) {
+ SkDebugf("outputdir expects one argument.\n");
+ continue;
+ }
+ outputDir.set(argv[i]);
+ continue;
+ }
+ if (!strcmp(argv[i], "--threshold")) {
+ colorThreshold = atoi(argv[++i]);
+ continue;
+ }
+ if (!strcmp(argv[i], "-u")) {
+ //we don't produce unified diffs, ignore parameter to work with svn diff
+ continue;
+ }
+ if (!strcmp(argv[i], "-L")) {
+ if (argc == ++i) {
+ SkDebugf("label expects one argument.\n");
+ continue;
+ }
+ switch (numLabelArguments++) {
+ case 0:
+ baseLabel.set(argv[i]);
+ continue;
+ case 1:
+ comparisonLabel.set(argv[i]);
+ continue;
+ default:
+ SkDebugf("extra label argument <%s>\n", argv[i]);
+ usage(argv[0]);
+ return kGenericError;
+ }
+ continue;
+ }
+ if (argv[i][0] != '-') {
+ switch (numUnflaggedArguments++) {
+ case 0:
+ baseFile.set(argv[i]);
+ continue;
+ case 1:
+ comparisonFile.set(argv[i]);
+ continue;
+ default:
+ SkDebugf("extra unflagged argument <%s>\n", argv[i]);
+ usage(argv[0]);
+ return kGenericError;
+ }
+ }
+
+ SkDebugf("Unrecognized argument <%s>\n", argv[i]);
+ usage(argv[0]);
+ return kGenericError;
+ }
+
+ if (numUnflaggedArguments != 2) {
+ usage(argv[0]);
+ return kGenericError;
+ }
+
+ if (listFilenames) {
+ printf("Base file is [%s]\n", baseFile.c_str());
+ }
+
+ if (listFilenames) {
+ printf("Comparison file is [%s]\n", comparisonFile.c_str());
+ }
+
+ if (outputDir.isEmpty()) {
+ if (listFilenames) {
+ printf("Not writing any diffs. No output dir specified.\n");
+ }
+ } else {
+ if (!outputDir.endsWith(PATH_DIV_STR)) {
+ outputDir.append(PATH_DIV_STR);
+ }
+ if (listFilenames) {
+ printf("Writing diffs. Output dir is [%s]\n", outputDir.c_str());
+ }
+ }
+
+ // Some obscure documentation about diff/patch labels:
+ //
+ // Posix says the format is: <filename><tab><date>
+ // It also states that if a filename contains <tab> or <newline>
+ // the result is implementation defined
+ //
+ // Svn diff --diff-cmd provides labels of the form: <filename><tab><revision>
+ //
+ // Git diff --ext-diff does not supply arguments compatible with diff.
+ // However, it does provide the filename directly.
+ // skimagediff_git.sh: skimagediff %2 %5 -L "%1\t(%3)" -L "%1\t(%6)"
+ //
+ // Git difftool sets $LOCAL, $REMOTE, $MERGED, and $BASE instead of command line parameters.
+ // difftool.<>.cmd: skimagediff $LOCAL $REMOTE -L "$MERGED\t(local)" -L "$MERGED\t(remote)"
+ //
+ // Diff will write any specified label verbatim. Without a specified label diff will write
+ // <filename><tab><date>
+ // However, diff will encode the filename as a cstring if the filename contains
+ // Any of <space> or <double quote>
+ // A char less than 32
+ // Any escapable character \a, \b, \t, \n, \v, \f, \r, \\
+ //
+ // Patch decodes:
+ // If first <non-white-space> is <double quote>, parse filename from cstring.
+ // If there is a <tab> after the first <non-white-space>, filename is
+ // [first <non-white-space>, the next run of <white-space> with an embedded <tab>).
+ // Otherwise the filename is [first <non-space>, the next <white-space>).
+ //
+ // The filename /dev/null means the file does not exist (used in adds and deletes).
+
+ // Considering the above, skimagediff will consider the contents of a -L parameter as
+ // <filename>(\t<specifier>)?
+ SkString outputFile;
+
+ if (baseLabel.isEmpty()) {
+ baseLabel.set(baseFile);
+ outputFile = baseLabel;
+ } else {
+ const char* baseLabelCstr = baseLabel.c_str();
+ const char* tab = strchr(baseLabelCstr, '\t');
+ if (NULL == tab) {
+ outputFile = baseLabel;
+ } else {
+ outputFile.set(baseLabelCstr, tab - baseLabelCstr);
+ }
+ }
+ if (comparisonLabel.isEmpty()) {
+ comparisonLabel.set(comparisonFile);
+ }
+ printf("Base: %s\n", baseLabel.c_str());
+ printf("Comparison: %s\n", comparisonLabel.c_str());
+
+ DiffRecord dr;
+ create_diff_images(diffProc, colorThreshold, baseFile, comparisonFile, outputDir, outputFile,
+ &dr);
+
+ if (DiffResource::isStatusFailed(dr.fBase.fStatus)) {
+ printf("Base %s.\n", DiffResource::getStatusDescription(dr.fBase.fStatus));
+ }
+ if (DiffResource::isStatusFailed(dr.fComparison.fStatus)) {
+ printf("Comparison %s.\n", DiffResource::getStatusDescription(dr.fComparison.fStatus));
+ }
+ printf("Base and Comparison %s.\n", DiffRecord::getResultDescription(dr.fResult));
+
+ if (DiffRecord::kDifferentPixels_Result == dr.fResult) {
+ printf("%.4f%% of pixels differ", 100 * dr.fFractionDifference);
+ printf(" (%.4f%% weighted)", 100 * dr.fWeightedFraction);
+ if (dr.fFractionDifference < 0.01) {
+ printf(" %d pixels", static_cast<int>(dr.fFractionDifference *
+ dr.fBase.fBitmap.width() *
+ dr.fBase.fBitmap.height()));
+ }
+
+ printf("\nAverage color mismatch: ");
+ printf("%d", static_cast<int>(MAX3(dr.fAverageMismatchR,
+ dr.fAverageMismatchG,
+ dr.fAverageMismatchB)));
+ printf("\nMax color mismatch: ");
+ printf("%d", MAX3(dr.fMaxMismatchR,
+ dr.fMaxMismatchG,
+ dr.fMaxMismatchB));
+ printf("\n");
+ }
+ printf("\n");
+
+ int num_failing_results = 0;
+ if (failOnResultType[dr.fResult]) {
+ ++num_failing_results;
+ }
+ if (failOnStatusType[dr.fBase.fStatus][dr.fComparison.fStatus]) {
+ ++num_failing_results;
+ }
+
+ return num_failing_results;
+}
+
+#if !defined SK_BUILD_FOR_IOS
+int main(int argc, char * const argv[]) {
+ return tool_main(argc, (char**) argv);
+}
+#endif
diff --git a/tools/skdiff_main.cpp b/tools/skdiff_main.cpp
new file mode 100644
index 0000000000..a9a1968fa5
--- /dev/null
+++ b/tools/skdiff_main.cpp
@@ -0,0 +1,773 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "skdiff.h"
+#include "skdiff_html.h"
+#include "skdiff_utils.h"
+#include "SkBitmap.h"
+#include "SkData.h"
+#include "SkImageDecoder.h"
+#include "SkImageEncoder.h"
+#include "SkOSFile.h"
+#include "SkStream.h"
+#include "SkTDArray.h"
+#include "SkTemplates.h"
+#include "SkTSearch.h"
+#include "SkTypes.h"
+
+/**
+ * skdiff
+ *
+ * Given three directory names, expects to find identically-named files in
+ * each of the first two; the first are treated as a set of baseline,
+ * the second a set of variant images, and a diff image is written into the
+ * third directory for each pair.
+ * Creates an index.html in the current third directory to compare each
+ * pair that does not match exactly.
+ * Recursively descends directories, unless run with --norecurse.
+ *
+ * Returns zero exit code if all images match across baseDir and comparisonDir.
+ */
+
+typedef SkTDArray<SkString*> StringArray;
+typedef StringArray FileArray;
+
+struct DiffSummary {
+ DiffSummary ()
+ : fNumMatches(0)
+ , fNumMismatches(0)
+ , fMaxMismatchV(0)
+ , fMaxMismatchPercent(0) { };
+
+ ~DiffSummary() {
+ for (int i = 0; i < DiffRecord::kResultCount; ++i) {
+ fResultsOfType[i].deleteAll();
+ }
+ for (int base = 0; base < DiffResource::kStatusCount; ++base) {
+ for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
+ fStatusOfType[base][comparison].deleteAll();
+ }
+ }
+ }
+
+ uint32_t fNumMatches;
+ uint32_t fNumMismatches;
+ uint32_t fMaxMismatchV;
+ float fMaxMismatchPercent;
+
+ FileArray fResultsOfType[DiffRecord::kResultCount];
+ FileArray fStatusOfType[DiffResource::kStatusCount][DiffResource::kStatusCount];
+
+ void printContents(const FileArray& fileArray,
+ const char* baseStatus, const char* comparisonStatus,
+ bool listFilenames) {
+ int n = fileArray.count();
+ printf("%d file pairs %s in baseDir and %s in comparisonDir",
+ n, baseStatus, comparisonStatus);
+ if (listFilenames) {
+ printf(": ");
+ for (int i = 0; i < n; ++i) {
+ printf("%s ", fileArray[i]->c_str());
+ }
+ }
+ printf("\n");
+ }
+
+ void printStatus(bool listFilenames,
+ bool failOnStatusType[DiffResource::kStatusCount]
+ [DiffResource::kStatusCount]) {
+ typedef DiffResource::Status Status;
+
+ for (int base = 0; base < DiffResource::kStatusCount; ++base) {
+ Status baseStatus = static_cast<Status>(base);
+ for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
+ Status comparisonStatus = static_cast<Status>(comparison);
+ const FileArray& fileArray = fStatusOfType[base][comparison];
+ if (fileArray.count() > 0) {
+ if (failOnStatusType[base][comparison]) {
+ printf(" [*] ");
+ } else {
+ printf(" [_] ");
+ }
+ printContents(fileArray,
+ DiffResource::getStatusDescription(baseStatus),
+ DiffResource::getStatusDescription(comparisonStatus),
+ listFilenames);
+ }
+ }
+ }
+ }
+
+ // Print a line about the contents of this FileArray to stdout.
+ void printContents(const FileArray& fileArray, const char* headerText, bool listFilenames) {
+ int n = fileArray.count();
+ printf("%d file pairs %s", n, headerText);
+ if (listFilenames) {
+ printf(": ");
+ for (int i = 0; i < n; ++i) {
+ printf("%s ", fileArray[i]->c_str());
+ }
+ }
+ printf("\n");
+ }
+
+ void print(bool listFilenames, bool failOnResultType[DiffRecord::kResultCount],
+ bool failOnStatusType[DiffResource::kStatusCount]
+ [DiffResource::kStatusCount]) {
+ printf("\ncompared %d file pairs:\n", fNumMatches + fNumMismatches);
+ for (int resultInt = 0; resultInt < DiffRecord::kResultCount; ++resultInt) {
+ DiffRecord::Result result = static_cast<DiffRecord::Result>(resultInt);
+ if (failOnResultType[result]) {
+ printf("[*] ");
+ } else {
+ printf("[_] ");
+ }
+ printContents(fResultsOfType[result], DiffRecord::getResultDescription(result),
+ listFilenames);
+ if (DiffRecord::kCouldNotCompare_Result == result) {
+ printStatus(listFilenames, failOnStatusType);
+ }
+ }
+ printf("(results marked with [*] will cause nonzero return value)\n");
+ printf("\nnumber of mismatching file pairs: %d\n", fNumMismatches);
+ if (fNumMismatches > 0) {
+ printf("Maximum pixel intensity mismatch %d\n", fMaxMismatchV);
+ printf("Largest area mismatch was %.2f%% of pixels\n",fMaxMismatchPercent);
+ }
+ }
+
+ void add (DiffRecord* drp) {
+ uint32_t mismatchValue;
+
+ if (drp->fBase.fFilename.equals(drp->fComparison.fFilename)) {
+ fResultsOfType[drp->fResult].push(new SkString(drp->fBase.fFilename));
+ } else {
+ SkString* blame = new SkString("(");
+ blame->append(drp->fBase.fFilename);
+ blame->append(", ");
+ blame->append(drp->fComparison.fFilename);
+ blame->append(")");
+ fResultsOfType[drp->fResult].push(blame);
+ }
+ switch (drp->fResult) {
+ case DiffRecord::kEqualBits_Result:
+ fNumMatches++;
+ break;
+ case DiffRecord::kEqualPixels_Result:
+ fNumMatches++;
+ break;
+ case DiffRecord::kDifferentSizes_Result:
+ fNumMismatches++;
+ break;
+ case DiffRecord::kDifferentPixels_Result:
+ fNumMismatches++;
+ if (drp->fFractionDifference * 100 > fMaxMismatchPercent) {
+ fMaxMismatchPercent = drp->fFractionDifference * 100;
+ }
+ mismatchValue = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG,
+ drp->fMaxMismatchB);
+ if (mismatchValue > fMaxMismatchV) {
+ fMaxMismatchV = mismatchValue;
+ }
+ break;
+ case DiffRecord::kCouldNotCompare_Result:
+ fNumMismatches++;
+ fStatusOfType[drp->fBase.fStatus][drp->fComparison.fStatus].push(
+ new SkString(drp->fBase.fFilename));
+ break;
+ case DiffRecord::kUnknown_Result:
+ SkDEBUGFAIL("adding uncategorized DiffRecord");
+ break;
+ default:
+ SkDEBUGFAIL("adding DiffRecord with unhandled fResult value");
+ break;
+ }
+ }
+};
+
+/// Returns true if string contains any of these substrings.
+static bool string_contains_any_of(const SkString& string,
+ const StringArray& substrings) {
+ for (int i = 0; i < substrings.count(); i++) {
+ if (string.contains(substrings[i]->c_str())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/// Internal (potentially recursive) implementation of get_file_list.
+static void get_file_list_subdir(const SkString& rootDir, const SkString& subDir,
+ const StringArray& matchSubstrings,
+ const StringArray& nomatchSubstrings,
+ bool recurseIntoSubdirs, FileArray *files) {
+ bool isSubDirEmpty = subDir.isEmpty();
+ SkString dir(rootDir);
+ if (!isSubDirEmpty) {
+ dir.append(PATH_DIV_STR);
+ dir.append(subDir);
+ }
+
+ // Iterate over files (not directories) within dir.
+ SkOSFile::Iter fileIterator(dir.c_str());
+ SkString fileName;
+ while (fileIterator.next(&fileName, false)) {
+ if (fileName.startsWith(".")) {
+ continue;
+ }
+ SkString pathRelativeToRootDir(subDir);
+ if (!isSubDirEmpty) {
+ pathRelativeToRootDir.append(PATH_DIV_STR);
+ }
+ pathRelativeToRootDir.append(fileName);
+ if (string_contains_any_of(pathRelativeToRootDir, matchSubstrings) &&
+ !string_contains_any_of(pathRelativeToRootDir, nomatchSubstrings)) {
+ files->push(new SkString(pathRelativeToRootDir));
+ }
+ }
+
+ // Recurse into any non-ignored subdirectories.
+ if (recurseIntoSubdirs) {
+ SkOSFile::Iter dirIterator(dir.c_str());
+ SkString dirName;
+ while (dirIterator.next(&dirName, true)) {
+ if (dirName.startsWith(".")) {
+ continue;
+ }
+ SkString pathRelativeToRootDir(subDir);
+ if (!isSubDirEmpty) {
+ pathRelativeToRootDir.append(PATH_DIV_STR);
+ }
+ pathRelativeToRootDir.append(dirName);
+ if (!string_contains_any_of(pathRelativeToRootDir, nomatchSubstrings)) {
+ get_file_list_subdir(rootDir, pathRelativeToRootDir,
+ matchSubstrings, nomatchSubstrings, recurseIntoSubdirs,
+ files);
+ }
+ }
+ }
+}
+
+/// Iterate over dir and get all files whose filename:
+/// - matches any of the substrings in matchSubstrings, but...
+/// - DOES NOT match any of the substrings in nomatchSubstrings
+/// - DOES NOT start with a dot (.)
+/// Adds the matching files to the list in *files.
+static void get_file_list(const SkString& dir,
+ const StringArray& matchSubstrings,
+ const StringArray& nomatchSubstrings,
+ bool recurseIntoSubdirs, FileArray *files) {
+ get_file_list_subdir(dir, SkString(""),
+ matchSubstrings, nomatchSubstrings, recurseIntoSubdirs,
+ files);
+}
+
+static void release_file_list(FileArray *files) {
+ files->deleteAll();
+}
+
+/// Comparison routines for qsort, sort by file names.
+static int compare_file_name_metrics(SkString **lhs, SkString **rhs) {
+ return strcmp((*lhs)->c_str(), (*rhs)->c_str());
+}
+
+class AutoReleasePixels {
+public:
+ AutoReleasePixels(DiffRecord* drp)
+ : fDrp(drp) {
+ SkASSERT(drp != NULL);
+ }
+ ~AutoReleasePixels() {
+ fDrp->fBase.fBitmap.setPixelRef(NULL);
+ fDrp->fComparison.fBitmap.setPixelRef(NULL);
+ fDrp->fDifference.fBitmap.setPixelRef(NULL);
+ fDrp->fWhite.fBitmap.setPixelRef(NULL);
+ }
+
+private:
+ DiffRecord* fDrp;
+};
+
+static void get_bounds(DiffResource& resource, const char* name) {
+ if (resource.fBitmap.empty() && !DiffResource::isStatusFailed(resource.fStatus)) {
+ SkAutoDataUnref fileBits(read_file(resource.fFullPath.c_str()));
+ if (NULL == fileBits) {
+ SkDebugf("WARNING: couldn't read %s file <%s>\n", name, resource.fFullPath.c_str());
+ resource.fStatus = DiffResource::kCouldNotRead_Status;
+ } else {
+ get_bitmap(fileBits, resource, SkImageDecoder::kDecodeBounds_Mode);
+ }
+ }
+}
+
+static void get_bounds(DiffRecord& drp) {
+ get_bounds(drp.fBase, "base");
+ get_bounds(drp.fComparison, "comparison");
+}
+
+/// Creates difference images, returns the number that have a 0 metric.
+/// If outputDir.isEmpty(), don't write out diff files.
+static void create_diff_images (DiffMetricProc dmp,
+ const int colorThreshold,
+ RecordArray* differences,
+ const SkString& baseDir,
+ const SkString& comparisonDir,
+ const SkString& outputDir,
+ const StringArray& matchSubstrings,
+ const StringArray& nomatchSubstrings,
+ bool recurseIntoSubdirs,
+ bool getBounds,
+ DiffSummary* summary) {
+ SkASSERT(!baseDir.isEmpty());
+ SkASSERT(!comparisonDir.isEmpty());
+
+ FileArray baseFiles;
+ FileArray comparisonFiles;
+
+ get_file_list(baseDir, matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, &baseFiles);
+ get_file_list(comparisonDir, matchSubstrings, nomatchSubstrings, recurseIntoSubdirs,
+ &comparisonFiles);
+
+ if (!baseFiles.isEmpty()) {
+ qsort(baseFiles.begin(), baseFiles.count(), sizeof(SkString*),
+ SkCastForQSort(compare_file_name_metrics));
+ }
+ if (!comparisonFiles.isEmpty()) {
+ qsort(comparisonFiles.begin(), comparisonFiles.count(),
+ sizeof(SkString*), SkCastForQSort(compare_file_name_metrics));
+ }
+
+ int i = 0;
+ int j = 0;
+
+ while (i < baseFiles.count() &&
+ j < comparisonFiles.count()) {
+
+ SkString basePath(baseDir);
+ SkString comparisonPath(comparisonDir);
+
+ DiffRecord *drp = new DiffRecord;
+ int v = strcmp(baseFiles[i]->c_str(), comparisonFiles[j]->c_str());
+
+ if (v < 0) {
+ // in baseDir, but not in comparisonDir
+ drp->fResult = DiffRecord::kCouldNotCompare_Result;
+
+ basePath.append(*baseFiles[i]);
+ comparisonPath.append(*baseFiles[i]);
+
+ drp->fBase.fFilename = *baseFiles[i];
+ drp->fBase.fFullPath = basePath;
+ drp->fBase.fStatus = DiffResource::kExists_Status;
+
+ drp->fComparison.fFilename = *baseFiles[i];
+ drp->fComparison.fFullPath = comparisonPath;
+ drp->fComparison.fStatus = DiffResource::kDoesNotExist_Status;
+
+ ++i;
+ } else if (v > 0) {
+ // in comparisonDir, but not in baseDir
+ drp->fResult = DiffRecord::kCouldNotCompare_Result;
+
+ basePath.append(*comparisonFiles[j]);
+ comparisonPath.append(*comparisonFiles[j]);
+
+ drp->fBase.fFilename = *comparisonFiles[j];
+ drp->fBase.fFullPath = basePath;
+ drp->fBase.fStatus = DiffResource::kDoesNotExist_Status;
+
+ drp->fComparison.fFilename = *comparisonFiles[j];
+ drp->fComparison.fFullPath = comparisonPath;
+ drp->fComparison.fStatus = DiffResource::kExists_Status;
+
+ ++j;
+ } else {
+ // Found the same filename in both baseDir and comparisonDir.
+ SkASSERT(DiffRecord::kUnknown_Result == drp->fResult);
+
+ basePath.append(*baseFiles[i]);
+ comparisonPath.append(*comparisonFiles[j]);
+
+ drp->fBase.fFilename = *baseFiles[i];
+ drp->fBase.fFullPath = basePath;
+ drp->fBase.fStatus = DiffResource::kExists_Status;
+
+ drp->fComparison.fFilename = *comparisonFiles[j];
+ drp->fComparison.fFullPath = comparisonPath;
+ drp->fComparison.fStatus = DiffResource::kExists_Status;
+
+ SkAutoDataUnref baseFileBits(read_file(drp->fBase.fFullPath.c_str()));
+ if (NULL != baseFileBits) {
+ drp->fBase.fStatus = DiffResource::kRead_Status;
+ }
+ SkAutoDataUnref comparisonFileBits(read_file(drp->fComparison.fFullPath.c_str()));
+ if (NULL != comparisonFileBits) {
+ drp->fComparison.fStatus = DiffResource::kRead_Status;
+ }
+ if (NULL == baseFileBits || NULL == comparisonFileBits) {
+ if (NULL == baseFileBits) {
+ drp->fBase.fStatus = DiffResource::kCouldNotRead_Status;
+ }
+ if (NULL == comparisonFileBits) {
+ drp->fComparison.fStatus = DiffResource::kCouldNotRead_Status;
+ }
+ drp->fResult = DiffRecord::kCouldNotCompare_Result;
+
+ } else if (are_buffers_equal(baseFileBits, comparisonFileBits)) {
+ drp->fResult = DiffRecord::kEqualBits_Result;
+
+ } else {
+ AutoReleasePixels arp(drp);
+ get_bitmap(baseFileBits, drp->fBase, SkImageDecoder::kDecodePixels_Mode);
+ get_bitmap(comparisonFileBits, drp->fComparison,
+ SkImageDecoder::kDecodePixels_Mode);
+ if (DiffResource::kDecoded_Status == drp->fBase.fStatus &&
+ DiffResource::kDecoded_Status == drp->fComparison.fStatus) {
+ create_and_write_diff_image(drp, dmp, colorThreshold,
+ outputDir, drp->fBase.fFilename);
+ } else {
+ drp->fResult = DiffRecord::kCouldNotCompare_Result;
+ }
+ }
+
+ ++i;
+ ++j;
+ }
+
+ if (getBounds) {
+ get_bounds(*drp);
+ }
+ SkASSERT(DiffRecord::kUnknown_Result != drp->fResult);
+ differences->push(drp);
+ summary->add(drp);
+ }
+
+ for (; i < baseFiles.count(); ++i) {
+ // files only in baseDir
+ DiffRecord *drp = new DiffRecord();
+ drp->fBase.fFilename = *baseFiles[i];
+ drp->fBase.fFullPath = baseDir;
+ drp->fBase.fFullPath.append(drp->fBase.fFilename);
+ drp->fBase.fStatus = DiffResource::kExists_Status;
+
+ drp->fComparison.fFilename = *baseFiles[i];
+ drp->fComparison.fFullPath = comparisonDir;
+ drp->fComparison.fFullPath.append(drp->fComparison.fFilename);
+ drp->fComparison.fStatus = DiffResource::kDoesNotExist_Status;
+
+ drp->fResult = DiffRecord::kCouldNotCompare_Result;
+ if (getBounds) {
+ get_bounds(*drp);
+ }
+ differences->push(drp);
+ summary->add(drp);
+ }
+
+ for (; j < comparisonFiles.count(); ++j) {
+ // files only in comparisonDir
+ DiffRecord *drp = new DiffRecord();
+ drp->fBase.fFilename = *comparisonFiles[j];
+ drp->fBase.fFullPath = baseDir;
+ drp->fBase.fFullPath.append(drp->fBase.fFilename);
+ drp->fBase.fStatus = DiffResource::kDoesNotExist_Status;
+
+ drp->fComparison.fFilename = *comparisonFiles[j];
+ drp->fComparison.fFullPath = comparisonDir;
+ drp->fComparison.fFullPath.append(drp->fComparison.fFilename);
+ drp->fComparison.fStatus = DiffResource::kExists_Status;
+
+ drp->fResult = DiffRecord::kCouldNotCompare_Result;
+ if (getBounds) {
+ get_bounds(*drp);
+ }
+ differences->push(drp);
+ summary->add(drp);
+ }
+
+ release_file_list(&baseFiles);
+ release_file_list(&comparisonFiles);
+}
+
+static void usage (char * argv0) {
+ SkDebugf("Skia baseline image diff tool\n");
+ SkDebugf("\n"
+"Usage: \n"
+" %s <baseDir> <comparisonDir> [outputDir] \n", argv0);
+ SkDebugf(
+"\nArguments:"
+"\n --failonresult <result>: After comparing all file pairs, exit with nonzero"
+"\n return code (number of file pairs yielding this"
+"\n result) if any file pairs yielded this result."
+"\n This flag may be repeated, in which case the"
+"\n return code will be the number of fail pairs"
+"\n yielding ANY of these results."
+"\n --failonstatus <baseStatus> <comparisonStatus>: exit with nonzero return"
+"\n code if any file pairs yielded this status."
+"\n --help: display this info"
+"\n --listfilenames: list all filenames for each result type in stdout"
+"\n --match <substring>: compare files whose filenames contain this substring;"
+"\n if unspecified, compare ALL files."
+"\n this flag may be repeated."
+"\n --nodiffs: don't write out image diffs or index.html, just generate"
+"\n report on stdout"
+"\n --nomatch <substring>: regardless of --match, DO NOT compare files whose"
+"\n filenames contain this substring."
+"\n this flag may be repeated."
+"\n --noprintdirs: do not print the directories used."
+"\n --norecurse: do not recurse into subdirectories."
+"\n --sortbymaxmismatch: sort by worst color channel mismatch;"
+"\n break ties with -sortbymismatch"
+"\n --sortbymismatch: sort by average color channel mismatch"
+"\n --threshold <n>: only report differences > n (per color channel) [default 0]"
+"\n --weighted: sort by # pixels different weighted by color difference"
+"\n"
+"\n baseDir: directory to read baseline images from."
+"\n comparisonDir: directory to read comparison images from"
+"\n outputDir: directory to write difference images and index.html to;"
+"\n defaults to comparisonDir"
+"\n"
+"\nIf no sort is specified, it will sort by fraction of pixels mismatching."
+"\n");
+}
+
+const int kNoError = 0;
+const int kGenericError = -1;
+
+int tool_main(int argc, char** argv);
+int tool_main(int argc, char** argv) {
+ DiffMetricProc diffProc = compute_diff_pmcolor;
+ int (*sortProc)(const void*, const void*) = compare<CompareDiffMetrics>;
+
+ // Maximum error tolerated in any one color channel in any one pixel before
+ // a difference is reported.
+ int colorThreshold = 0;
+ SkString baseDir;
+ SkString comparisonDir;
+ SkString outputDir;
+
+ StringArray matchSubstrings;
+ StringArray nomatchSubstrings;
+
+ bool generateDiffs = true;
+ bool listFilenames = false;
+ bool printDirNames = true;
+ bool recurseIntoSubdirs = true;
+
+ RecordArray differences;
+ DiffSummary summary;
+
+ bool failOnResultType[DiffRecord::kResultCount];
+ for (int i = 0; i < DiffRecord::kResultCount; i++) {
+ failOnResultType[i] = false;
+ }
+
+ bool failOnStatusType[DiffResource::kStatusCount][DiffResource::kStatusCount];
+ for (int base = 0; base < DiffResource::kStatusCount; ++base) {
+ for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
+ failOnStatusType[base][comparison] = false;
+ }
+ }
+
+ int i;
+ int numUnflaggedArguments = 0;
+ for (i = 1; i < argc; i++) {
+ if (!strcmp(argv[i], "--failonresult")) {
+ if (argc == ++i) {
+ SkDebugf("failonresult expects one argument.\n");
+ continue;
+ }
+ DiffRecord::Result type = DiffRecord::getResultByName(argv[i]);
+ if (type != DiffRecord::kResultCount) {
+ failOnResultType[type] = true;
+ } else {
+ SkDebugf("ignoring unrecognized result <%s>\n", argv[i]);
+ }
+ continue;
+ }
+ if (!strcmp(argv[i], "--failonstatus")) {
+ if (argc == ++i) {
+ SkDebugf("failonstatus missing base status.\n");
+ continue;
+ }
+ bool baseStatuses[DiffResource::kStatusCount];
+ if (!DiffResource::getMatchingStatuses(argv[i], baseStatuses)) {
+ SkDebugf("unrecognized base status <%s>\n", argv[i]);
+ }
+
+ if (argc == ++i) {
+ SkDebugf("failonstatus missing comparison status.\n");
+ continue;
+ }
+ bool comparisonStatuses[DiffResource::kStatusCount];
+ if (!DiffResource::getMatchingStatuses(argv[i], comparisonStatuses)) {
+ SkDebugf("unrecognized comarison status <%s>\n", argv[i]);
+ }
+
+ for (int base = 0; base < DiffResource::kStatusCount; ++base) {
+ for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
+ failOnStatusType[base][comparison] |=
+ baseStatuses[base] && comparisonStatuses[comparison];
+ }
+ }
+ continue;
+ }
+ if (!strcmp(argv[i], "--help")) {
+ usage(argv[0]);
+ return kNoError;
+ }
+ if (!strcmp(argv[i], "--listfilenames")) {
+ listFilenames = true;
+ continue;
+ }
+ if (!strcmp(argv[i], "--match")) {
+ matchSubstrings.push(new SkString(argv[++i]));
+ continue;
+ }
+ if (!strcmp(argv[i], "--nodiffs")) {
+ generateDiffs = false;
+ continue;
+ }
+ if (!strcmp(argv[i], "--nomatch")) {
+ nomatchSubstrings.push(new SkString(argv[++i]));
+ continue;
+ }
+ if (!strcmp(argv[i], "--noprintdirs")) {
+ printDirNames = false;
+ continue;
+ }
+ if (!strcmp(argv[i], "--norecurse")) {
+ recurseIntoSubdirs = false;
+ continue;
+ }
+ if (!strcmp(argv[i], "--sortbymaxmismatch")) {
+ sortProc = compare<CompareDiffMaxMismatches>;
+ continue;
+ }
+ if (!strcmp(argv[i], "--sortbymismatch")) {
+ sortProc = compare<CompareDiffMeanMismatches>;
+ continue;
+ }
+ if (!strcmp(argv[i], "--threshold")) {
+ colorThreshold = atoi(argv[++i]);
+ continue;
+ }
+ if (!strcmp(argv[i], "--weighted")) {
+ sortProc = compare<CompareDiffWeighted>;
+ continue;
+ }
+ if (argv[i][0] != '-') {
+ switch (numUnflaggedArguments++) {
+ case 0:
+ baseDir.set(argv[i]);
+ continue;
+ case 1:
+ comparisonDir.set(argv[i]);
+ continue;
+ case 2:
+ outputDir.set(argv[i]);
+ continue;
+ default:
+ SkDebugf("extra unflagged argument <%s>\n", argv[i]);
+ usage(argv[0]);
+ return kGenericError;
+ }
+ }
+
+ SkDebugf("Unrecognized argument <%s>\n", argv[i]);
+ usage(argv[0]);
+ return kGenericError;
+ }
+
+ if (numUnflaggedArguments == 2) {
+ outputDir = comparisonDir;
+ } else if (numUnflaggedArguments != 3) {
+ usage(argv[0]);
+ return kGenericError;
+ }
+
+ if (!baseDir.endsWith(PATH_DIV_STR)) {
+ baseDir.append(PATH_DIV_STR);
+ }
+ if (printDirNames) {
+ printf("baseDir is [%s]\n", baseDir.c_str());
+ }
+
+ if (!comparisonDir.endsWith(PATH_DIV_STR)) {
+ comparisonDir.append(PATH_DIV_STR);
+ }
+ if (printDirNames) {
+ printf("comparisonDir is [%s]\n", comparisonDir.c_str());
+ }
+
+ if (!outputDir.endsWith(PATH_DIV_STR)) {
+ outputDir.append(PATH_DIV_STR);
+ }
+ if (generateDiffs) {
+ if (printDirNames) {
+ printf("writing diffs to outputDir is [%s]\n", outputDir.c_str());
+ }
+ } else {
+ if (printDirNames) {
+ printf("not writing any diffs to outputDir [%s]\n", outputDir.c_str());
+ }
+ outputDir.set("");
+ }
+
+ // If no matchSubstrings were specified, match ALL strings
+ // (except for whatever nomatchSubstrings were specified, if any).
+ if (matchSubstrings.isEmpty()) {
+ matchSubstrings.push(new SkString(""));
+ }
+
+ create_diff_images(diffProc, colorThreshold, &differences,
+ baseDir, comparisonDir, outputDir,
+ matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, generateDiffs,
+ &summary);
+ summary.print(listFilenames, failOnResultType, failOnStatusType);
+
+ if (differences.count()) {
+ qsort(differences.begin(), differences.count(),
+ sizeof(DiffRecord*), sortProc);
+ }
+
+ if (generateDiffs) {
+ print_diff_page(summary.fNumMatches, colorThreshold, differences,
+ baseDir, comparisonDir, outputDir);
+ }
+
+ for (i = 0; i < differences.count(); i++) {
+ delete differences[i];
+ }
+ matchSubstrings.deleteAll();
+ nomatchSubstrings.deleteAll();
+
+ int num_failing_results = 0;
+ for (int i = 0; i < DiffRecord::kResultCount; i++) {
+ if (failOnResultType[i]) {
+ num_failing_results += summary.fResultsOfType[i].count();
+ }
+ }
+ if (!failOnResultType[DiffRecord::kCouldNotCompare_Result]) {
+ for (int base = 0; base < DiffResource::kStatusCount; ++base) {
+ for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
+ if (failOnStatusType[base][comparison]) {
+ num_failing_results += summary.fStatusOfType[base][comparison].count();
+ }
+ }
+ }
+ }
+
+ // On Linux (and maybe other platforms too), any results outside of the
+ // range [0...255] are wrapped (mod 256). Do the conversion ourselves, to
+ // make sure that we only return 0 when there were no failures.
+ return (num_failing_results > 255) ? 255 : num_failing_results;
+}
+
+#if !defined SK_BUILD_FOR_IOS
+int main(int argc, char * const argv[]) {
+ return tool_main(argc, (char**) argv);
+}
+#endif
diff --git a/tools/skdiff_utils.cpp b/tools/skdiff_utils.cpp
new file mode 100644
index 0000000000..0eb405aaa5
--- /dev/null
+++ b/tools/skdiff_utils.cpp
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "skdiff.h"
+#include "skdiff_utils.h"
+#include "SkBitmap.h"
+#include "SkData.h"
+#include "SkImageDecoder.h"
+#include "SkImageEncoder.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+#include "SkTypes.h"
+
+bool are_buffers_equal(SkData* skdata1, SkData* skdata2) {
+ if ((NULL == skdata1) || (NULL == skdata2)) {
+ return false;
+ }
+ if (skdata1->size() != skdata2->size()) {
+ return false;
+ }
+ return (0 == memcmp(skdata1->data(), skdata2->data(), skdata1->size()));
+}
+
+SkData* read_file(const char* file_path) {
+ SkFILEStream fileStream(file_path);
+ if (!fileStream.isValid()) {
+ SkDebugf("WARNING: could not open file <%s> for reading\n", file_path);
+ return NULL;
+ }
+ size_t bytesInFile = fileStream.getLength();
+ size_t bytesLeftToRead = bytesInFile;
+
+ void* bufferStart = sk_malloc_throw(bytesInFile);
+ char* bufferPointer = (char*)bufferStart;
+ while (bytesLeftToRead > 0) {
+ size_t bytesReadThisTime = fileStream.read(bufferPointer, bytesLeftToRead);
+ if (0 == bytesReadThisTime) {
+ SkDebugf("WARNING: error reading from <%s>\n", file_path);
+ sk_free(bufferStart);
+ return NULL;
+ }
+ bytesLeftToRead -= bytesReadThisTime;
+ bufferPointer += bytesReadThisTime;
+ }
+ return SkData::NewFromMalloc(bufferStart, bytesInFile);
+}
+
+bool get_bitmap(SkData* fileBits, DiffResource& resource, SkImageDecoder::Mode mode) {
+ SkMemoryStream stream(fileBits->data(), fileBits->size());
+
+ SkImageDecoder* codec = SkImageDecoder::Factory(&stream);
+ if (NULL == codec) {
+ SkDebugf("ERROR: no codec found for <%s>\n", resource.fFullPath.c_str());
+ resource.fStatus = DiffResource::kCouldNotDecode_Status;
+ return false;
+ }
+
+ // In debug, the DLL will automatically be unloaded when this is deleted,
+ // but that shouldn't be a problem in release mode.
+ SkAutoTDelete<SkImageDecoder> ad(codec);
+
+ stream.rewind();
+ if (!codec->decode(&stream, &resource.fBitmap, SkBitmap::kARGB_8888_Config, mode)) {
+ SkDebugf("ERROR: codec failed for basePath <%s>\n", resource.fFullPath.c_str());
+ resource.fStatus = DiffResource::kCouldNotDecode_Status;
+ return false;
+ }
+
+ resource.fStatus = DiffResource::kDecoded_Status;
+ return true;
+}
+
+/** Thanks to PNG, we need to force all pixels 100% opaque. */
+static void force_all_opaque(const SkBitmap& bitmap) {
+ SkAutoLockPixels lock(bitmap);
+ for (int y = 0; y < bitmap.height(); y++) {
+ for (int x = 0; x < bitmap.width(); x++) {
+ *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
+ }
+ }
+}
+
+bool write_bitmap(const SkString& path, const SkBitmap& bitmap) {
+ SkBitmap copy;
+ bitmap.copyTo(&copy, SkBitmap::kARGB_8888_Config);
+ force_all_opaque(copy);
+ return SkImageEncoder::EncodeFile(path.c_str(), copy,
+ SkImageEncoder::kPNG_Type, 100);
+}
+
+/// Return a copy of the "input" string, within which we have replaced all instances
+/// of oldSubstring with newSubstring.
+///
+/// TODO: If we like this, we should move it into the core SkString implementation,
+/// adding more checks and ample test cases, and paying more attention to efficiency.
+static SkString replace_all(const SkString &input,
+ const char oldSubstring[], const char newSubstring[]) {
+ SkString output;
+ const char *input_cstr = input.c_str();
+ const char *first_char = input_cstr;
+ const char *match_char;
+ int oldSubstringLen = strlen(oldSubstring);
+ while (NULL != (match_char = strstr(first_char, oldSubstring))) {
+ output.append(first_char, (match_char - first_char));
+ output.append(newSubstring);
+ first_char = match_char + oldSubstringLen;
+ }
+ output.append(first_char);
+ return output;
+}
+
+static SkString filename_to_derived_filename(const SkString& filename, const char *suffix) {
+ SkString diffName (filename);
+ const char* cstring = diffName.c_str();
+ int dotOffset = strrchr(cstring, '.') - cstring;
+ diffName.remove(dotOffset, diffName.size() - dotOffset);
+ diffName.append(suffix);
+
+ // In case we recursed into subdirectories, replace slashes with something else
+ // so the diffs will all be written into a single flat directory.
+ diffName = replace_all(diffName, PATH_DIV_STR, "_");
+ return diffName;
+}
+
+SkString filename_to_diff_filename(const SkString& filename) {
+ return filename_to_derived_filename(filename, "-diff.png");
+}
+
+SkString filename_to_white_filename(const SkString& filename) {
+ return filename_to_derived_filename(filename, "-white.png");
+}
+
+void create_and_write_diff_image(DiffRecord* drp,
+ DiffMetricProc dmp,
+ const int colorThreshold,
+ const SkString& outputDir,
+ const SkString& filename) {
+ const int w = drp->fBase.fBitmap.width();
+ const int h = drp->fBase.fBitmap.height();
+
+ if (w != drp->fComparison.fBitmap.width() || h != drp->fComparison.fBitmap.height()) {
+ drp->fResult = DiffRecord::kDifferentSizes_Result;
+ } else {
+ drp->fDifference.fBitmap.setConfig(SkBitmap::kARGB_8888_Config, w, h);
+ drp->fDifference.fBitmap.allocPixels();
+
+ drp->fWhite.fBitmap.setConfig(SkBitmap::kARGB_8888_Config, w, h);
+ drp->fWhite.fBitmap.allocPixels();
+
+ SkASSERT(DiffRecord::kUnknown_Result == drp->fResult);
+ compute_diff(drp, dmp, colorThreshold);
+ SkASSERT(DiffRecord::kUnknown_Result != drp->fResult);
+ }
+
+ if (outputDir.isEmpty()) {
+ drp->fDifference.fStatus = DiffResource::kUnspecified_Status;
+ drp->fWhite.fStatus = DiffResource::kUnspecified_Status;
+
+ } else {
+ drp->fDifference.fFilename = filename_to_diff_filename(filename);
+ drp->fDifference.fFullPath = outputDir;
+ drp->fDifference.fFullPath.append(drp->fDifference.fFilename);
+ drp->fDifference.fStatus = DiffResource::kSpecified_Status;
+
+ drp->fWhite.fFilename = filename_to_white_filename(filename);
+ drp->fWhite.fFullPath = outputDir;
+ drp->fWhite.fFullPath.append(drp->fWhite.fFilename);
+ drp->fWhite.fStatus = DiffResource::kSpecified_Status;
+
+ if (DiffRecord::kDifferentPixels_Result == drp->fResult) {
+ if (write_bitmap(drp->fDifference.fFullPath, drp->fDifference.fBitmap)) {
+ drp->fDifference.fStatus = DiffResource::kExists_Status;
+ } else {
+ drp->fDifference.fStatus = DiffResource::kDoesNotExist_Status;
+ }
+ if (write_bitmap(drp->fWhite.fFullPath, drp->fWhite.fBitmap)) {
+ drp->fWhite.fStatus = DiffResource::kExists_Status;
+ } else {
+ drp->fWhite.fStatus = DiffResource::kDoesNotExist_Status;
+ }
+ }
+ }
+}
diff --git a/tools/skdiff_utils.h b/tools/skdiff_utils.h
new file mode 100644
index 0000000000..00ebf899a9
--- /dev/null
+++ b/tools/skdiff_utils.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef skdiff_utils_DEFINED
+#define skdiff_utils_DEFINED
+
+#include "skdiff.h"
+#include "SkImageDecoder.h"
+
+class SkBitmap;
+class SkData;
+class SkString;
+
+/** Returns true if the two buffers passed in are both non-NULL,
+ * have the same length, and contain exactly the same byte values.
+ */
+bool are_buffers_equal(SkData* skdata1, SkData* skdata2);
+
+/** Reads the file at the given path and returns its complete contents as an
+ * SkData object (or returns NULL on error).
+ */
+SkData* read_file(const char* file_path);
+
+/** Decodes the fileBits into the resource.fBitmap. Returns false on failure. */
+bool get_bitmap(SkData* fileBits, DiffResource& resource, SkImageDecoder::Mode mode);
+
+/** Writes the bitmap as a PNG to the path specified. */
+bool write_bitmap(const SkString& path, const SkBitmap& bitmap);
+
+/** Given an image filename, returns the name of the file containing
+ * the associated difference image.
+ */
+SkString filename_to_diff_filename(const SkString& filename);
+
+/** Given an image filename, returns the name of the file containing
+ * the "white" difference image.
+ */
+SkString filename_to_white_filename(const SkString& filename);
+
+/** Calls compute_diff and handles the difference and white diff resources.
+ * If !outputDir.isEmpty(), writes out difference and white images.
+ */
+void create_and_write_diff_image(DiffRecord* drp,
+ DiffMetricProc dmp,
+ const int colorThreshold,
+ const SkString& outputDir,
+ const SkString& filename);
+
+#endif
diff --git a/tools/tests/bench_pictures_cfg_test.py b/tools/tests/bench_pictures_cfg_test.py
new file mode 100644
index 0000000000..77ad553b28
--- /dev/null
+++ b/tools/tests/bench_pictures_cfg_test.py
@@ -0,0 +1,46 @@
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+"""
+Verify that the bench_pictures.cfg file is sane.
+"""
+
+
+import os
+import sys
+
+
+def ThrowIfNotAString(obj):
+ """ Raise a TypeError if obj is not a string. """
+ if str(obj) != obj:
+ raise TypeError('%s is not a string!' % str(obj))
+
+
+def Main(argv):
+ """ Verify that the bench_pictures.cfg file is sane.
+
+ - Exec the file to ensure that it uses correct Python syntax.
+ - Make sure that every element is a string, because the buildbot scripts will
+ fail to execute if this is not the case.
+
+ This test does not verify that the well-formed configs are actually valid.
+ """
+ vars = {'import_path': 'tools'}
+ execfile(os.path.join('tools', 'bench_pictures.cfg'), vars)
+ bench_pictures_cfg = vars['bench_pictures_cfg']
+
+ for config_name, config_list in bench_pictures_cfg.iteritems():
+ ThrowIfNotAString(config_name)
+ for config in config_list:
+ for key, value in config.iteritems():
+ ThrowIfNotAString(key)
+ if type(value).__name__ == 'list':
+ for item in value:
+ ThrowIfNotAString(item)
+ else:
+ ThrowIfNotAString(value)
+
+if __name__ == '__main__':
+ sys.exit(Main(sys.argv)) \ No newline at end of file
diff --git a/tools/tests/run.sh b/tools/tests/run.sh
new file mode 100755
index 0000000000..1acb124017
--- /dev/null
+++ b/tools/tests/run.sh
@@ -0,0 +1,70 @@
+#!/bin/bash
+
+# Tests for our tools.
+# TODO: for now, it only tests skdiff
+
+# cd into .../trunk so all the paths will work
+cd $(dirname $0)/../..
+
+# TODO: make it look in Release and/or Debug
+SKDIFF_BINARY=out/Debug/skdiff
+
+# Compare contents of all files within directories $1 and $2,
+# EXCEPT for any dotfiles.
+# If there are any differences, a description is written to stdout and
+# we exit with a nonzero return value.
+# Otherwise, we write nothing to stdout and return.
+function compare_directories {
+ if [ $# != 2 ]; then
+ echo "compare_directories requires exactly 2 parameters, got $#"
+ exit 1
+ fi
+ diff --exclude=.* $1 $2
+ if [ $? != 0 ]; then
+ echo "failed in: compare_directories $1 $2"
+ exit 1
+ fi
+}
+
+# Run skdiff with arguments in $1 (plus implicit final argument causing skdiff
+# to write its output, if any, to directory $2/output-actual).
+# Then compare its results against those in $2/output-expected.
+function skdiff_test {
+ if [ $# != 2 ]; then
+ echo "skdiff_test requires exactly 2 parameters, got $#"
+ exit 1
+ fi
+ SKDIFF_ARGS="$1"
+ ACTUAL_OUTPUT_DIR="$2/output-actual"
+ EXPECTED_OUTPUT_DIR="$2/output-expected"
+
+ rm -rf $ACTUAL_OUTPUT_DIR
+ mkdir -p $ACTUAL_OUTPUT_DIR
+ COMMAND="$SKDIFF_BINARY $SKDIFF_ARGS $ACTUAL_OUTPUT_DIR"
+ echo "$COMMAND" >$ACTUAL_OUTPUT_DIR/command_line
+ $COMMAND &>$ACTUAL_OUTPUT_DIR/stdout
+ echo $? >$ACTUAL_OUTPUT_DIR/return_value
+
+ compare_directories $EXPECTED_OUTPUT_DIR $ACTUAL_OUTPUT_DIR
+}
+
+SKDIFF_TESTDIR=tools/tests/skdiff
+
+# Run skdiff over a variety of file pair types: identical bits, identical pixels, missing from
+# baseDir, etc.
+skdiff_test "$SKDIFF_TESTDIR/baseDir $SKDIFF_TESTDIR/comparisonDir" "$SKDIFF_TESTDIR/test1"
+
+# Run skdiff over the same set of files, but with arguments as used by our buildbots:
+# - return the number of mismatching file pairs (but ignore any files missing from either
+# baseDir or comparisonDir)
+# - list filenames with each result type to stdout
+# - don't generate HTML output files
+skdiff_test "--failonresult DifferentPixels --failonresult DifferentSizes --failonresult Unknown --failonstatus CouldNotDecode,CouldNotRead any --failonstatus any CouldNotDecode,CouldNotRead --listfilenames --nodiffs $SKDIFF_TESTDIR/baseDir $SKDIFF_TESTDIR/comparisonDir" "$SKDIFF_TESTDIR/test2"
+
+# Run skdiff over just the files that have identical bits.
+skdiff_test "--nodiffs --match identical-bits $SKDIFF_TESTDIR/baseDir $SKDIFF_TESTDIR/comparisonDir" "$SKDIFF_TESTDIR/identical-bits"
+
+# Run skdiff over just the files that have identical bits or identical pixels.
+skdiff_test "--nodiffs --match identical-bits --match identical-pixels $SKDIFF_TESTDIR/baseDir $SKDIFF_TESTDIR/comparisonDir" "$SKDIFF_TESTDIR/identical-bits-or-pixels"
+
+echo "All tests passed."
diff --git a/tools/tests/skdiff/identical-bits-or-pixels/output-expected/stdout b/tools/tests/skdiff/identical-bits-or-pixels/output-expected/stdout
new file mode 100644
index 0000000000..a0800cff8d
--- /dev/null
+++ b/tools/tests/skdiff/identical-bits-or-pixels/output-expected/stdout
@@ -0,0 +1,14 @@
+baseDir is [tools/tests/skdiff/baseDir/]
+comparisonDir is [tools/tests/skdiff/comparisonDir/]
+not writing any diffs to outputDir [tools/tests/skdiff/identical-bits-or-pixels/output-actual/]
+
+compared 3 file pairs:
+[_] 2 file pairs contain exactly the same bits
+[_] 1 file pairs contain the same pixel values, but not the same bits
+[_] 0 file pairs have identical dimensions but some differing pixels
+[_] 0 file pairs have differing dimensions
+[_] 0 file pairs could not be compared
+[_] 0 file pairs not compared yet
+(results marked with [*] will cause nonzero return value)
+
+number of mismatching file pairs: 0
diff --git a/tools/tests/skdiff/identical-bits/output-expected/stdout b/tools/tests/skdiff/identical-bits/output-expected/stdout
new file mode 100644
index 0000000000..d05c1cc709
--- /dev/null
+++ b/tools/tests/skdiff/identical-bits/output-expected/stdout
@@ -0,0 +1,14 @@
+baseDir is [tools/tests/skdiff/baseDir/]
+comparisonDir is [tools/tests/skdiff/comparisonDir/]
+not writing any diffs to outputDir [tools/tests/skdiff/identical-bits/output-actual/]
+
+compared 2 file pairs:
+[_] 2 file pairs contain exactly the same bits
+[_] 0 file pairs contain the same pixel values, but not the same bits
+[_] 0 file pairs have identical dimensions but some differing pixels
+[_] 0 file pairs have differing dimensions
+[_] 0 file pairs could not be compared
+[_] 0 file pairs not compared yet
+(results marked with [*] will cause nonzero return value)
+
+number of mismatching file pairs: 0
diff --git a/tools/tests/skdiff/test1/output-expected/index.html b/tools/tests/skdiff/test1/output-expected/index.html
new file mode 100644
index 0000000000..a3e192e482
--- /dev/null
+++ b/tools/tests/skdiff/test1/output-expected/index.html
@@ -0,0 +1,50 @@
+<html>
+<head>
+<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
+<script type="text/javascript">
+function generateCheckedList() {
+var boxes = $(":checkbox:checked");
+var fileCmdLineString = '';
+var fileMultiLineString = '';
+for (var i = 0; i < boxes.length; i++) {
+fileMultiLineString += boxes[i].name + '<br>';
+fileCmdLineString += boxes[i].name + '&nbsp;';
+}
+$("#checkedList").html(fileCmdLineString + '<br><br>' + fileMultiLineString);
+}
+</script>
+</head>
+<body>
+<table>
+<tr><th>select image</th>
+<th>3 of 12 diffs matched exactly.<br></th>
+<th>every different pixel shown in white</th>
+<th>color difference at each pixel</th>
+<th>baseDir: tools/tests/skdiff/baseDir/</th>
+<th>comparisonDir: tools/tests/skdiff/comparisonDir/</th>
+</tr>
+<tr>
+<td><input type="checkbox" name="different-bits/different-bits-unknown-format.xyz" checked="yes"></td><td><b>different-bits/different-bits-unknown-format.xyz</b><br>Could not compare.<br>base: could not be decoded<br>comparison: could not be decoded</td><td>N/A</td><td>N/A</td><td><a href="../../../../../tools/tests/skdiff/baseDir/different-bits/different-bits-unknown-format.xyz">N/A</a></td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/different-bits/different-bits-unknown-format.xyz">N/A</a></td></tr>
+<tr>
+<td><input type="checkbox" name="missing-files/missing-from-baseDir.png" checked="yes"></td><td><b>missing-files/missing-from-baseDir.png</b><br>Could not compare.<br>base: not found<br>comparison: decoded</td><td>N/A</td><td>N/A</td><td>N/A</td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/missing-files/missing-from-baseDir.png"><img src="../../../../../tools/tests/skdiff/comparisonDir/missing-files/missing-from-baseDir.png" height="240px"></a></td></tr>
+<tr>
+<td><input type="checkbox" name="missing-files/missing-from-baseDir.xyz" checked="yes"></td><td><b>missing-files/missing-from-baseDir.xyz</b><br>Could not compare.<br>base: not found<br>comparison: could not be decoded</td><td>N/A</td><td>N/A</td><td>N/A</td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/missing-files/missing-from-baseDir.xyz">N/A</a></td></tr>
+<tr>
+<td><input type="checkbox" name="missing-files/missing-from-comparisonDir.png" checked="yes"></td><td><b>missing-files/missing-from-comparisonDir.png</b><br>Could not compare.<br>base: decoded<br>comparison: not found</td><td>N/A</td><td>N/A</td><td><a href="../../../../../tools/tests/skdiff/baseDir/missing-files/missing-from-comparisonDir.png"><img src="../../../../../tools/tests/skdiff/baseDir/missing-files/missing-from-comparisonDir.png" height="240px"></a></td><td>N/A</td></tr>
+<tr>
+<td><input type="checkbox" name="missing-files/missing-from-comparisonDir.xyz" checked="yes"></td><td><b>missing-files/missing-from-comparisonDir.xyz</b><br>Could not compare.<br>base: could not be decoded<br>comparison: not found</td><td>N/A</td><td>N/A</td><td><a href="../../../../../tools/tests/skdiff/baseDir/missing-files/missing-from-comparisonDir.xyz">N/A</a></td><td>N/A</td></tr>
+<tr>
+<td><input type="checkbox" name="different-bits/slightly-different-sizes.png" checked="yes"></td><td><b>different-bits/slightly-different-sizes.png</b><br>Image sizes differ</td><td>N/A</td><td>N/A</td><td><a href="../../../../../tools/tests/skdiff/baseDir/different-bits/slightly-different-sizes.png"><img src="../../../../../tools/tests/skdiff/baseDir/different-bits/slightly-different-sizes.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/different-bits/slightly-different-sizes.png"><img src="../../../../../tools/tests/skdiff/comparisonDir/different-bits/slightly-different-sizes.png" height="240px"></a></td></tr>
+<tr>
+<td><input type="checkbox" name="different-bits/very-different-sizes.png" checked="yes"></td><td><b>different-bits/very-different-sizes.png</b><br>Image sizes differ</td><td>N/A</td><td>N/A</td><td><a href="../../../../../tools/tests/skdiff/baseDir/different-bits/very-different-sizes.png"><img src="../../../../../tools/tests/skdiff/baseDir/different-bits/very-different-sizes.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/different-bits/very-different-sizes.png"><img src="../../../../../tools/tests/skdiff/comparisonDir/different-bits/very-different-sizes.png" height="128px"></a></td></tr>
+<tr>
+<td><input type="checkbox" name="different-bits/very-different-pixels-same-size.png" checked="yes"></td><td><b>different-bits/very-different-pixels-same-size.png</b><br>97.9926% of pixels differ
+ (42.8911% weighted)<br>Average color mismatch 89<br>Max color mismatch 239</td><td><a href="different-bits_very-different-pixels-same-size-white.png"><img src="different-bits_very-different-pixels-same-size-white.png" height="240px"></a></td><td><a href="different-bits_very-different-pixels-same-size-diff.png"><img src="different-bits_very-different-pixels-same-size-diff.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/baseDir/different-bits/very-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/baseDir/different-bits/very-different-pixels-same-size.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/different-bits/very-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/comparisonDir/different-bits/very-different-pixels-same-size.png" height="240px"></a></td></tr>
+<tr>
+<td><input type="checkbox" name="different-bits/slightly-different-pixels-same-size.png" checked="yes"></td><td><b>different-bits/slightly-different-pixels-same-size.png</b><br>0.6630% of pixels differ
+ (0.1904% weighted)<br>(2164 pixels)<br>Average color mismatch 0<br>Max color mismatch 213</td><td><a href="different-bits_slightly-different-pixels-same-size-white.png"><img src="different-bits_slightly-different-pixels-same-size-white.png" height="240px"></a></td><td><a href="different-bits_slightly-different-pixels-same-size-diff.png"><img src="different-bits_slightly-different-pixels-same-size-diff.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/baseDir/different-bits/slightly-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/baseDir/different-bits/slightly-different-pixels-same-size.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/different-bits/slightly-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/comparisonDir/different-bits/slightly-different-pixels-same-size.png" height="240px"></a></td></tr>
+</table>
+<input type="button" onclick="generateCheckedList()" value="Create Rebaseline List">
+<div id="checkedList"></div>
+</body>
+</html>
diff --git a/tools/tests/skdiff/test1/output-expected/stdout b/tools/tests/skdiff/test1/output-expected/stdout
new file mode 100644
index 0000000000..2b8b2d403f
--- /dev/null
+++ b/tools/tests/skdiff/test1/output-expected/stdout
@@ -0,0 +1,27 @@
+ERROR: no codec found for <tools/tests/skdiff/baseDir/different-bits/different-bits-unknown-format.xyz>
+ERROR: no codec found for <tools/tests/skdiff/comparisonDir/different-bits/different-bits-unknown-format.xyz>
+ERROR: no codec found for <tools/tests/skdiff/baseDir/identical-bits/identical-bits-unknown-format.xyz>
+ERROR: no codec found for <tools/tests/skdiff/comparisonDir/identical-bits/identical-bits-unknown-format.xyz>
+ERROR: no codec found for <tools/tests/skdiff/comparisonDir/missing-files/missing-from-baseDir.xyz>
+ERROR: no codec found for <tools/tests/skdiff/baseDir/missing-files/missing-from-comparisonDir.xyz>
+baseDir is [tools/tests/skdiff/baseDir/]
+comparisonDir is [tools/tests/skdiff/comparisonDir/]
+writing diffs to outputDir is [tools/tests/skdiff/test1/output-actual/]
+
+compared 12 file pairs:
+[_] 2 file pairs contain exactly the same bits
+[_] 1 file pairs contain the same pixel values, but not the same bits
+[_] 2 file pairs have identical dimensions but some differing pixels
+[_] 2 file pairs have differing dimensions
+[_] 5 file pairs could not be compared
+ [_] 1 file pairs decoded in baseDir and not found in comparisonDir
+ [_] 1 file pairs could not be decoded in baseDir and could not be decoded in comparisonDir
+ [_] 1 file pairs could not be decoded in baseDir and not found in comparisonDir
+ [_] 1 file pairs not found in baseDir and decoded in comparisonDir
+ [_] 1 file pairs not found in baseDir and could not be decoded in comparisonDir
+[_] 0 file pairs not compared yet
+(results marked with [*] will cause nonzero return value)
+
+number of mismatching file pairs: 9
+Maximum pixel intensity mismatch 239
+Largest area mismatch was 97.99% of pixels
diff --git a/tools/tests/skdiff/test2/output-expected/command_line b/tools/tests/skdiff/test2/output-expected/command_line
new file mode 100644
index 0000000000..2d8cc5716f
--- /dev/null
+++ b/tools/tests/skdiff/test2/output-expected/command_line
@@ -0,0 +1 @@
+out/Debug/skdiff --failonresult DifferentPixels --failonresult DifferentSizes --failonresult Unknown --failonstatus CouldNotDecode,CouldNotRead any --failonstatus any CouldNotDecode,CouldNotRead --listfilenames --nodiffs tools/tests/skdiff/baseDir tools/tests/skdiff/comparisonDir tools/tests/skdiff/test2/output-actual
diff --git a/tools/tests/skdiff/test2/output-expected/stdout b/tools/tests/skdiff/test2/output-expected/stdout
new file mode 100644
index 0000000000..6aa33b8fd4
--- /dev/null
+++ b/tools/tests/skdiff/test2/output-expected/stdout
@@ -0,0 +1,21 @@
+ERROR: no codec found for <tools/tests/skdiff/baseDir/different-bits/different-bits-unknown-format.xyz>
+ERROR: no codec found for <tools/tests/skdiff/comparisonDir/different-bits/different-bits-unknown-format.xyz>
+baseDir is [tools/tests/skdiff/baseDir/]
+comparisonDir is [tools/tests/skdiff/comparisonDir/]
+not writing any diffs to outputDir [tools/tests/skdiff/test2/output-actual/]
+
+compared 12 file pairs:
+[_] 2 file pairs contain exactly the same bits: identical-bits/identical-bits-unknown-format.xyz identical-bits/identical-bits.png
+[_] 1 file pairs contain the same pixel values, but not the same bits: different-bits/different-bits-identical-pixels.png
+[*] 2 file pairs have identical dimensions but some differing pixels: different-bits/slightly-different-pixels-same-size.png different-bits/very-different-pixels-same-size.png
+[*] 2 file pairs have differing dimensions: different-bits/slightly-different-sizes.png different-bits/very-different-sizes.png
+[_] 5 file pairs could not be compared: different-bits/different-bits-unknown-format.xyz missing-files/missing-from-baseDir.png missing-files/missing-from-baseDir.xyz missing-files/missing-from-comparisonDir.png missing-files/missing-from-comparisonDir.xyz
+ [*] 1 file pairs could not be decoded in baseDir and could not be decoded in comparisonDir: different-bits/different-bits-unknown-format.xyz
+ [_] 2 file pairs found in baseDir and not found in comparisonDir: missing-files/missing-from-comparisonDir.png missing-files/missing-from-comparisonDir.xyz
+ [_] 2 file pairs not found in baseDir and found in comparisonDir: missing-files/missing-from-baseDir.png missing-files/missing-from-baseDir.xyz
+[*] 0 file pairs not compared yet:
+(results marked with [*] will cause nonzero return value)
+
+number of mismatching file pairs: 9
+Maximum pixel intensity mismatch 239
+Largest area mismatch was 97.99% of pixels